first draft of a MoveHistory window
[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 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 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h"
129
130 #ifdef ENABLE_NLS
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
133 #else
134 # define _(s) (s)
135 # define N_(s) s
136 #endif
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
247 int chattingPartner;
248
249 /* States for ics_getting_history */
250 #define H_FALSE 0
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
256
257 /* whosays values for GameEnds */
258 #define GE_ICS 0
259 #define GE_ENGINE 1
260 #define GE_PLAYER 2
261 #define GE_FILE 3
262 #define GE_XBOARD 4
263 #define GE_ENGINE1 5
264 #define GE_ENGINE2 6
265
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
268
269 /* Different types of move when calling RegisterMove */
270 #define CMAIL_MOVE   0
271 #define CMAIL_RESIGN 1
272 #define CMAIL_DRAW   2
273 #define CMAIL_ACCEPT 3
274
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
279
280 /* Telnet protocol constants */
281 #define TN_WILL 0373
282 #define TN_WONT 0374
283 #define TN_DO   0375
284 #define TN_DONT 0376
285 #define TN_IAC  0377
286 #define TN_ECHO 0001
287 #define TN_SGA  0003
288 #define TN_PORT 23
289
290 /* [AS] */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
292 {
293     assert( dst != NULL );
294     assert( src != NULL );
295     assert( count > 0 );
296
297     strncpy( dst, src, count );
298     dst[ count-1 ] = '\0';
299     return dst;
300 }
301
302 #if 0
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
305 {
306     size_t  dst_len;
307
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     dst_len = strlen(dst);
313
314     assert( count > dst_len ); /* Buffer size must be greater than current length */
315
316     safeStrCpy( dst + dst_len, src, count - dst_len );
317
318     return dst;
319 }
320 #endif
321
322 /* Some compiler can't cast u64 to double
323  * This function do the job for us:
324
325  * We use the highest bit for cast, this only
326  * works if the highest bit is not
327  * in use (This should not happen)
328  *
329  * We used this for all compiler
330  */
331 double
332 u64ToDouble(u64 value)
333 {
334   double r;
335   u64 tmp = value & u64Const(0x7fffffffffffffff);
336   r = (double)(s64)tmp;
337   if (value & u64Const(0x8000000000000000))
338        r +=  9.2233720368547758080e18; /* 2^63 */
339  return r;
340 }
341
342 /* Fake up flags for now, as we aren't keeping track of castling
343    availability yet. [HGM] Change of logic: the flag now only
344    indicates the type of castlings allowed by the rule of the game.
345    The actual rights themselves are maintained in the array
346    castlingRights, as part of the game history, and are not probed
347    by this function.
348  */
349 int
350 PosFlags(index)
351 {
352   int flags = F_ALL_CASTLE_OK;
353   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354   switch (gameInfo.variant) {
355   case VariantSuicide:
356     flags &= ~F_ALL_CASTLE_OK;
357   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358     flags |= F_IGNORE_CHECK;
359   case VariantLosers:
360     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361     break;
362   case VariantAtomic:
363     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
364     break;
365   case VariantKriegspiel:
366     flags |= F_KRIEGSPIEL_CAPTURE;
367     break;
368   case VariantCapaRandom:
369   case VariantFischeRandom:
370     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371   case VariantNoCastle:
372   case VariantShatranj:
373   case VariantCourier:
374     flags &= ~F_ALL_CASTLE_OK;
375     break;
376   default:
377     break;
378   }
379   return flags;
380 }
381
382 FILE *gameFileFP, *debugFP;
383
384 /*
385     [AS] Note: sometimes, the sscanf() function is used to parse the input
386     into a fixed-size buffer. Because of this, we must be prepared to
387     receive strings as long as the size of the input buffer, which is currently
388     set to 4K for Windows and 8K for the rest.
389     So, we must either allocate sufficiently large buffers here, or
390     reduce the size of the input buffer in the input reading part.
391 */
392
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
396
397 ChessProgramState first, second;
398
399 /* premove variables */
400 int premoveToX = 0;
401 int premoveToY = 0;
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
405 int gotPremove = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
408
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
411
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
439 int movesPerSession;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
444 int matchGame = 0;
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
448
449 /* animateTraining preserves the state of appData.animate
450  * when Training mode is activated. This allows the
451  * response to be animated when appData.animate == TRUE and
452  * appData.animateDragging == TRUE.
453  */
454 Boolean animateTraining;
455
456 GameInfo gameInfo;
457
458 AppData appData;
459
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char  epStatus[MAX_MOVES];
463 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int   initialRulePlies, FENrulePlies;
468 char  FENepStatus;
469 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 int loadFlag = 0;
471 int shuffleOpenings;
472 int mute; // mute all sounds
473
474 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499         BlackKing, BlackBishop, BlackKnight, BlackRook }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509
510 #if (BOARD_SIZE>=10)
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
516 };
517
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
523 };
524
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
529         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
530 };
531
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
534         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
536         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
537 };
538
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
541         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
543         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
544 };
545
546 #ifdef GOTHIC
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
549         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
551         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
552 };
553 #else // !GOTHIC
554 #define GothicArray CapablancaArray
555 #endif // !GOTHIC
556
557 #ifdef FALCON
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
560         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
562         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
563 };
564 #else // !FALCON
565 #define FalconArray CapablancaArray
566 #endif // !FALCON
567
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
574
575 #if (BOARD_SIZE>=12)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
581 };
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
585
586
587 Board initialPosition;
588
589
590 /* Convert str to a rating. Checks for special cases of "----",
591
592    "++++", etc. Also strips ()'s */
593 int
594 string_to_rating(str)
595   char *str;
596 {
597   while(*str && !isdigit(*str)) ++str;
598   if (!*str)
599     return 0;   /* One of the special "no rating" cases */
600   else
601     return atoi(str);
602 }
603
604 void
605 ClearProgramStats()
606 {
607     /* Init programStats */
608     programStats.movelist[0] = 0;
609     programStats.depth = 0;
610     programStats.nr_moves = 0;
611     programStats.moves_left = 0;
612     programStats.nodes = 0;
613     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
614     programStats.score = 0;
615     programStats.got_only_move = 0;
616     programStats.got_fail = 0;
617     programStats.line_is_book = 0;
618 }
619
620 void
621 InitBackEnd1()
622 {
623     int matched, min, sec;
624
625     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
626
627     GetTimeMark(&programStartTime);
628     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
629
630     ClearProgramStats();
631     programStats.ok_to_send = 1;
632     programStats.seen_stat = 0;
633
634     /*
635      * Initialize game list
636      */
637     ListNew(&gameList);
638
639
640     /*
641      * Internet chess server status
642      */
643     if (appData.icsActive) {
644         appData.matchMode = FALSE;
645         appData.matchGames = 0;
646 #if ZIPPY
647         appData.noChessProgram = !appData.zippyPlay;
648 #else
649         appData.zippyPlay = FALSE;
650         appData.zippyTalk = FALSE;
651         appData.noChessProgram = TRUE;
652 #endif
653         if (*appData.icsHelper != NULLCHAR) {
654             appData.useTelnet = TRUE;
655             appData.telnetProgram = appData.icsHelper;
656         }
657     } else {
658         appData.zippyTalk = appData.zippyPlay = FALSE;
659     }
660
661     /* [AS] Initialize pv info list [HGM] and game state */
662     {
663         int i, j;
664
665         for( i=0; i<MAX_MOVES; i++ ) {
666             pvInfoList[i].depth = -1;
667             epStatus[i]=EP_NONE;
668             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669         }
670     }
671
672     /*
673      * Parse timeControl resource
674      */
675     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676                           appData.movesPerSession)) {
677         char buf[MSG_SIZ];
678         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679         DisplayFatalError(buf, 0, 2);
680     }
681
682     /*
683      * Parse searchTime resource
684      */
685     if (*appData.searchTime != NULLCHAR) {
686         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
687         if (matched == 1) {
688             searchTime = min * 60;
689         } else if (matched == 2) {
690             searchTime = min * 60 + sec;
691         } else {
692             char buf[MSG_SIZ];
693             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694             DisplayFatalError(buf, 0, 2);
695         }
696     }
697
698     /* [AS] Adjudication threshold */
699     adjudicateLossThreshold = appData.adjudicateLossThreshold;
700
701     first.which = "first";
702     second.which = "second";
703     first.maybeThinking = second.maybeThinking = FALSE;
704     first.pr = second.pr = NoProc;
705     first.isr = second.isr = NULL;
706     first.sendTime = second.sendTime = 2;
707     first.sendDrawOffers = 1;
708     if (appData.firstPlaysBlack) {
709         first.twoMachinesColor = "black\n";
710         second.twoMachinesColor = "white\n";
711     } else {
712         first.twoMachinesColor = "white\n";
713         second.twoMachinesColor = "black\n";
714     }
715     first.program = appData.firstChessProgram;
716     second.program = appData.secondChessProgram;
717     first.host = appData.firstHost;
718     second.host = appData.secondHost;
719     first.dir = appData.firstDirectory;
720     second.dir = appData.secondDirectory;
721     first.other = &second;
722     second.other = &first;
723     first.initString = appData.initString;
724     second.initString = appData.secondInitString;
725     first.computerString = appData.firstComputerString;
726     second.computerString = appData.secondComputerString;
727     first.useSigint = second.useSigint = TRUE;
728     first.useSigterm = second.useSigterm = TRUE;
729     first.reuse = appData.reuseFirst;
730     second.reuse = appData.reuseSecond;
731     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
732     second.nps = appData.secondNPS;
733     first.useSetboard = second.useSetboard = FALSE;
734     first.useSAN = second.useSAN = FALSE;
735     first.usePing = second.usePing = FALSE;
736     first.lastPing = second.lastPing = 0;
737     first.lastPong = second.lastPong = 0;
738     first.usePlayother = second.usePlayother = FALSE;
739     first.useColors = second.useColors = TRUE;
740     first.useUsermove = second.useUsermove = FALSE;
741     first.sendICS = second.sendICS = FALSE;
742     first.sendName = second.sendName = appData.icsActive;
743     first.sdKludge = second.sdKludge = FALSE;
744     first.stKludge = second.stKludge = FALSE;
745     TidyProgramName(first.program, first.host, first.tidy);
746     TidyProgramName(second.program, second.host, second.tidy);
747     first.matchWins = second.matchWins = 0;
748     strcpy(first.variants, appData.variant);
749     strcpy(second.variants, appData.variant);
750     first.analysisSupport = second.analysisSupport = 2; /* detect */
751     first.analyzing = second.analyzing = FALSE;
752     first.initDone = second.initDone = FALSE;
753
754     /* New features added by Tord: */
755     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757     /* End of new features added by Tord. */
758     first.fenOverride  = appData.fenOverride1;
759     second.fenOverride = appData.fenOverride2;
760
761     /* [HGM] time odds: set factor for each machine */
762     first.timeOdds  = appData.firstTimeOdds;
763     second.timeOdds = appData.secondTimeOdds;
764     { int norm = 1;
765         if(appData.timeOddsMode) {
766             norm = first.timeOdds;
767             if(norm > second.timeOdds) norm = second.timeOdds;
768         }
769         first.timeOdds /= norm;
770         second.timeOdds /= norm;
771     }
772
773     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774     first.accumulateTC = appData.firstAccumulateTC;
775     second.accumulateTC = appData.secondAccumulateTC;
776     first.maxNrOfSessions = second.maxNrOfSessions = 1;
777
778     /* [HGM] debug */
779     first.debug = second.debug = FALSE;
780     first.supportsNPS = second.supportsNPS = UNKNOWN;
781
782     /* [HGM] options */
783     first.optionSettings  = appData.firstOptions;
784     second.optionSettings = appData.secondOptions;
785
786     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788     first.isUCI = appData.firstIsUCI; /* [AS] */
789     second.isUCI = appData.secondIsUCI; /* [AS] */
790     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
792
793     if (appData.firstProtocolVersion > PROTOVER ||
794         appData.firstProtocolVersion < 1) {
795       char buf[MSG_SIZ];
796       sprintf(buf, _("protocol version %d not supported"),
797               appData.firstProtocolVersion);
798       DisplayFatalError(buf, 0, 2);
799     } else {
800       first.protocolVersion = appData.firstProtocolVersion;
801     }
802
803     if (appData.secondProtocolVersion > PROTOVER ||
804         appData.secondProtocolVersion < 1) {
805       char buf[MSG_SIZ];
806       sprintf(buf, _("protocol version %d not supported"),
807               appData.secondProtocolVersion);
808       DisplayFatalError(buf, 0, 2);
809     } else {
810       second.protocolVersion = appData.secondProtocolVersion;
811     }
812
813     if (appData.icsActive) {
814         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
815     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816         appData.clockMode = FALSE;
817         first.sendTime = second.sendTime = 0;
818     }
819
820 #if ZIPPY
821     /* Override some settings from environment variables, for backward
822        compatibility.  Unfortunately it's not feasible to have the env
823        vars just set defaults, at least in xboard.  Ugh.
824     */
825     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826       ZippyInit();
827     }
828 #endif
829
830     if (appData.noChessProgram) {
831         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832         sprintf(programVersion, "%s", PACKAGE_STRING);
833     } else {
834 #if 0
835         char *p, *q;
836         q = first.program;
837         while (*q != ' ' && *q != NULLCHAR) q++;
838         p = q;
839         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841         sprintf(programVersion, "%s + ", PACKAGE_STRING);
842         strncat(programVersion, p, q - p);
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 #endif
848     }
849
850     if (!appData.icsActive) {
851       char buf[MSG_SIZ];
852       /* Check for variants that are supported only in ICS mode,
853          or not at all.  Some that are accepted here nevertheless
854          have bugs; see comments below.
855       */
856       VariantClass variant = StringToVariant(appData.variant);
857       switch (variant) {
858       case VariantBughouse:     /* need four players and two boards */
859       case VariantKriegspiel:   /* need to hide pieces and move details */
860       /* case VariantFischeRandom: (Fabien: moved below) */
861         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862         DisplayFatalError(buf, 0, 2);
863         return;
864
865       case VariantUnknown:
866       case VariantLoadable:
867       case Variant29:
868       case Variant30:
869       case Variant31:
870       case Variant32:
871       case Variant33:
872       case Variant34:
873       case Variant35:
874       case Variant36:
875       default:
876         sprintf(buf, _("Unknown variant name %s"), appData.variant);
877         DisplayFatalError(buf, 0, 2);
878         return;
879
880       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
881       case VariantFairy:      /* [HGM] TestLegality definitely off! */
882       case VariantGothic:     /* [HGM] should work */
883       case VariantCapablanca: /* [HGM] should work */
884       case VariantCourier:    /* [HGM] initial forced moves not implemented */
885       case VariantShogi:      /* [HGM] drops not tested for legality */
886       case VariantKnightmate: /* [HGM] should work */
887       case VariantCylinder:   /* [HGM] untested */
888       case VariantFalcon:     /* [HGM] untested */
889       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890                                  offboard interposition not understood */
891       case VariantNormal:     /* definitely works! */
892       case VariantWildCastle: /* pieces not automatically shuffled */
893       case VariantNoCastle:   /* pieces not automatically shuffled */
894       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895       case VariantLosers:     /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantSuicide:    /* should work except for win condition,
898                                  and doesn't know captures are mandatory */
899       case VariantGiveaway:   /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantTwoKings:   /* should work */
902       case VariantAtomic:     /* should work except for win condition */
903       case Variant3Check:     /* should work except for win condition */
904       case VariantShatranj:   /* should work except for all win conditions */
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 #if 0
1014     int matched, min, sec;
1015
1016     matched = sscanf(tc, "%d:%d", &min, &sec);
1017     if (matched == 1) {
1018         timeControl = min * 60 * 1000;
1019     } else if (matched == 2) {
1020         timeControl = (min * 60 + sec) * 1000;
1021     } else {
1022         return FALSE;
1023     }
1024 #else
1025     long tc1;
1026     long tc2;
1027     char buf[MSG_SIZ];
1028
1029     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1030     if(ti > 0) {
1031         if(mps)
1032              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033         else sprintf(buf, "+%s+%d", tc, ti);
1034     } else {
1035         if(mps)
1036              sprintf(buf, "+%d/%s", mps, tc);
1037         else sprintf(buf, "+%s", tc);
1038     }
1039     fullTimeControlString = StrSave(buf);
1040
1041     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042         return FALSE;
1043     }
1044
1045     if( *tc == '/' ) {
1046         /* Parse second time control */
1047         tc++;
1048
1049         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1050             return FALSE;
1051         }
1052
1053         if( tc2 == 0 ) {
1054             return FALSE;
1055         }
1056
1057         timeControl_2 = tc2 * 1000;
1058     }
1059     else {
1060         timeControl_2 = 0;
1061     }
1062
1063     if( tc1 == 0 ) {
1064         return FALSE;
1065     }
1066
1067     timeControl = tc1 * 1000;
1068 #endif
1069
1070     if (ti >= 0) {
1071         timeIncrement = ti * 1000;  /* convert to ms */
1072         movesPerSession = 0;
1073     } else {
1074         timeIncrement = 0;
1075         movesPerSession = mps;
1076     }
1077     return TRUE;
1078 }
1079
1080 void
1081 InitBackEnd2()
1082 {
1083   if (appData.debugMode) {
1084     fprintf(debugFP, "%s\n", programVersion);
1085   }
1086
1087   if (appData.matchGames > 0) {
1088     appData.matchMode = TRUE;
1089   } else if (appData.matchMode) {
1090     appData.matchGames = 1;
1091   }
1092   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093     appData.matchGames = appData.sameColorGames;
1094   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1097   }
1098   Reset(TRUE, FALSE);
1099   if (appData.noChessProgram || first.protocolVersion == 1) {
1100     InitBackEnd3();
1101   } else {
1102     /* kludge: allow timeout for initial "feature" commands */
1103     FreezeUI();
1104     DisplayMessage("", _("Starting chess program"));
1105     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106   }
1107 }
1108
1109 void
1110 InitBackEnd3 P((void))
1111 {
1112     GameMode initialMode;
1113     char buf[MSG_SIZ];
1114     int err;
1115
1116     InitChessProgram(&first, startedFromSetupPosition);
1117
1118
1119     if (appData.icsActive) {
1120 #ifdef WIN32
1121         /* [DM] Make a console window if needed [HGM] merged ifs */
1122         ConsoleCreate();
1123 #endif
1124         err = establish();
1125         if (err != 0) {
1126             if (*appData.icsCommPort != NULLCHAR) {
1127                 sprintf(buf, _("Could not open comm port %s"),
1128                         appData.icsCommPort);
1129             } else {
1130                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1131                         appData.icsHost, appData.icsPort);
1132             }
1133             DisplayFatalError(buf, err, 1);
1134             return;
1135         }
1136         SetICSMode();
1137         telnetISR =
1138           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139         fromUserISR =
1140           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141     } else if (appData.noChessProgram) {
1142         SetNCPMode();
1143     } else {
1144         SetGNUMode();
1145     }
1146
1147     if (*appData.cmailGameName != NULLCHAR) {
1148         SetCmailMode();
1149         OpenLoopback(&cmailPR);
1150         cmailISR =
1151           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152     }
1153
1154     ThawUI();
1155     DisplayMessage("", "");
1156     if (StrCaseCmp(appData.initialMode, "") == 0) {
1157       initialMode = BeginningOfGame;
1158     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159       initialMode = TwoMachinesPlay;
1160     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161       initialMode = AnalyzeFile;
1162     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163       initialMode = AnalyzeMode;
1164     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165       initialMode = MachinePlaysWhite;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167       initialMode = MachinePlaysBlack;
1168     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169       initialMode = EditGame;
1170     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171       initialMode = EditPosition;
1172     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173       initialMode = Training;
1174     } else {
1175       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176       DisplayFatalError(buf, 0, 2);
1177       return;
1178     }
1179
1180     if (appData.matchMode) {
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = TRUE;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         TwoMachinesEvent();
1209     } else if (*appData.cmailGameName != NULLCHAR) {
1210         /* Set up cmail mode */
1211         ReloadCmailMsgEvent(TRUE);
1212     } else {
1213         /* Set up other modes */
1214         if (initialMode == AnalyzeFile) {
1215           if (*appData.loadGameFile == NULLCHAR) {
1216             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217             return;
1218           }
1219         }
1220         if (*appData.loadGameFile != NULLCHAR) {
1221             (void) LoadGameFromFile(appData.loadGameFile,
1222                                     appData.loadGameIndex,
1223                                     appData.loadGameFile, TRUE);
1224         } else if (*appData.loadPositionFile != NULLCHAR) {
1225             (void) LoadPositionFromFile(appData.loadPositionFile,
1226                                         appData.loadPositionIndex,
1227                                         appData.loadPositionFile);
1228             /* [HGM] try to make self-starting even after FEN load */
1229             /* to allow automatic setup of fairy variants with wtm */
1230             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231                 gameMode = BeginningOfGame;
1232                 setboardSpoiledMachineBlack = 1;
1233             }
1234             /* [HGM] loadPos: make that every new game uses the setup */
1235             /* from file as long as we do not switch variant          */
1236             if(!blackPlaysFirst) { int i;
1237                 startedFromPositionFile = TRUE;
1238                 CopyBoard(filePosition, boards[0]);
1239                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1240             }
1241         }
1242         if (initialMode == AnalyzeMode) {
1243           if (appData.noChessProgram) {
1244             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249             return;
1250           }
1251           AnalyzeModeEvent();
1252         } else if (initialMode == AnalyzeFile) {
1253           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254           ShowThinkingEvent();
1255           AnalyzeFileEvent();
1256           AnalysisPeriodicEvent(1);
1257         } else if (initialMode == MachinePlaysWhite) {
1258           if (appData.noChessProgram) {
1259             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260                               0, 2);
1261             return;
1262           }
1263           if (appData.icsActive) {
1264             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265                               0, 2);
1266             return;
1267           }
1268           MachineWhiteEvent();
1269         } else if (initialMode == MachinePlaysBlack) {
1270           if (appData.noChessProgram) {
1271             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272                               0, 2);
1273             return;
1274           }
1275           if (appData.icsActive) {
1276             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277                               0, 2);
1278             return;
1279           }
1280           MachineBlackEvent();
1281         } else if (initialMode == TwoMachinesPlay) {
1282           if (appData.noChessProgram) {
1283             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284                               0, 2);
1285             return;
1286           }
1287           if (appData.icsActive) {
1288             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289                               0, 2);
1290             return;
1291           }
1292           TwoMachinesEvent();
1293         } else if (initialMode == EditGame) {
1294           EditGameEvent();
1295         } else if (initialMode == EditPosition) {
1296           EditPositionEvent();
1297         } else if (initialMode == Training) {
1298           if (*appData.loadGameFile == NULLCHAR) {
1299             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1300             return;
1301           }
1302           TrainingEvent();
1303         }
1304     }
1305 }
1306
1307 /*
1308  * Establish will establish a contact to a remote host.port.
1309  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310  *  used to talk to the host.
1311  * Returns 0 if okay, error code if not.
1312  */
1313 int
1314 establish()
1315 {
1316     char buf[MSG_SIZ];
1317
1318     if (*appData.icsCommPort != NULLCHAR) {
1319         /* Talk to the host through a serial comm port */
1320         return OpenCommPort(appData.icsCommPort, &icsPR);
1321
1322     } else if (*appData.gateway != NULLCHAR) {
1323         if (*appData.remoteShell == NULLCHAR) {
1324             /* Use the rcmd protocol to run telnet program on a gateway host */
1325             snprintf(buf, sizeof(buf), "%s %s %s",
1326                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1327             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1328
1329         } else {
1330             /* Use the rsh program to run telnet program on a gateway host */
1331             if (*appData.remoteUser == NULLCHAR) {
1332                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333                         appData.gateway, appData.telnetProgram,
1334                         appData.icsHost, appData.icsPort);
1335             } else {
1336                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337                         appData.remoteShell, appData.gateway,
1338                         appData.remoteUser, appData.telnetProgram,
1339                         appData.icsHost, appData.icsPort);
1340             }
1341             return StartChildProcess(buf, "", &icsPR);
1342
1343         }
1344     } else if (appData.useTelnet) {
1345         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1346
1347     } else {
1348         /* TCP socket interface differs somewhat between
1349            Unix and NT; handle details in the front end.
1350            */
1351         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352     }
1353 }
1354
1355 void
1356 show_bytes(fp, buf, count)
1357      FILE *fp;
1358      char *buf;
1359      int count;
1360 {
1361     while (count--) {
1362         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363             fprintf(fp, "\\%03o", *buf & 0xff);
1364         } else {
1365             putc(*buf, fp);
1366         }
1367         buf++;
1368     }
1369     fflush(fp);
1370 }
1371
1372 /* Returns an errno value */
1373 int
1374 OutputMaybeTelnet(pr, message, count, outError)
1375      ProcRef pr;
1376      char *message;
1377      int count;
1378      int *outError;
1379 {
1380     char buf[8192], *p, *q, *buflim;
1381     int left, newcount, outcount;
1382
1383     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384         *appData.gateway != NULLCHAR) {
1385         if (appData.debugMode) {
1386             fprintf(debugFP, ">ICS: ");
1387             show_bytes(debugFP, message, count);
1388             fprintf(debugFP, "\n");
1389         }
1390         return OutputToProcess(pr, message, count, outError);
1391     }
1392
1393     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1394     p = message;
1395     q = buf;
1396     left = count;
1397     newcount = 0;
1398     while (left) {
1399         if (q >= buflim) {
1400             if (appData.debugMode) {
1401                 fprintf(debugFP, ">ICS: ");
1402                 show_bytes(debugFP, buf, newcount);
1403                 fprintf(debugFP, "\n");
1404             }
1405             outcount = OutputToProcess(pr, buf, newcount, outError);
1406             if (outcount < newcount) return -1; /* to be sure */
1407             q = buf;
1408             newcount = 0;
1409         }
1410         if (*p == '\n') {
1411             *q++ = '\r';
1412             newcount++;
1413         } else if (((unsigned char) *p) == TN_IAC) {
1414             *q++ = (char) TN_IAC;
1415             newcount ++;
1416         }
1417         *q++ = *p++;
1418         newcount++;
1419         left--;
1420     }
1421     if (appData.debugMode) {
1422         fprintf(debugFP, ">ICS: ");
1423         show_bytes(debugFP, buf, newcount);
1424         fprintf(debugFP, "\n");
1425     }
1426     outcount = OutputToProcess(pr, buf, newcount, outError);
1427     if (outcount < newcount) return -1; /* to be sure */
1428     return count;
1429 }
1430
1431 void
1432 read_from_player(isr, closure, message, count, error)
1433      InputSourceRef isr;
1434      VOIDSTAR closure;
1435      char *message;
1436      int count;
1437      int error;
1438 {
1439     int outError, outCount;
1440     static int gotEof = 0;
1441
1442     /* Pass data read from player on to ICS */
1443     if (count > 0) {
1444         gotEof = 0;
1445         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446         if (outCount < count) {
1447             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448         }
1449     } else if (count < 0) {
1450         RemoveInputSource(isr);
1451         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452     } else if (gotEof++ > 0) {
1453         RemoveInputSource(isr);
1454         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1455     }
1456 }
1457
1458 void
1459 KeepAlive()
1460 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461     SendToICS("date\n");
1462     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1463 }
1464
1465 void
1466 SendToICS(s)
1467      char *s;
1468 {
1469     int count, outCount, outError;
1470
1471     if (icsPR == NULL) return;
1472
1473     count = strlen(s);
1474     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475     if (outCount < count) {
1476         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1477     }
1478 }
1479
1480 /* This is used for sending logon scripts to the ICS. Sending
1481    without a delay causes problems when using timestamp on ICC
1482    (at least on my machine). */
1483 void
1484 SendToICSDelayed(s,msdelay)
1485      char *s;
1486      long msdelay;
1487 {
1488     int count, outCount, outError;
1489
1490     if (icsPR == NULL) return;
1491
1492     count = strlen(s);
1493     if (appData.debugMode) {
1494         fprintf(debugFP, ">ICS: ");
1495         show_bytes(debugFP, s, count);
1496         fprintf(debugFP, "\n");
1497     }
1498     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1499                                       msdelay);
1500     if (outCount < count) {
1501         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1502     }
1503 }
1504
1505
1506 /* Remove all highlighting escape sequences in s
1507    Also deletes any suffix starting with '('
1508    */
1509 char *
1510 StripHighlightAndTitle(s)
1511      char *s;
1512 {
1513     static char retbuf[MSG_SIZ];
1514     char *p = retbuf;
1515
1516     while (*s != NULLCHAR) {
1517         while (*s == '\033') {
1518             while (*s != NULLCHAR && !isalpha(*s)) s++;
1519             if (*s != NULLCHAR) s++;
1520         }
1521         while (*s != NULLCHAR && *s != '\033') {
1522             if (*s == '(' || *s == '[') {
1523                 *p = NULLCHAR;
1524                 return retbuf;
1525             }
1526             *p++ = *s++;
1527         }
1528     }
1529     *p = NULLCHAR;
1530     return retbuf;
1531 }
1532
1533 /* Remove all highlighting escape sequences in s */
1534 char *
1535 StripHighlight(s)
1536      char *s;
1537 {
1538     static char retbuf[MSG_SIZ];
1539     char *p = retbuf;
1540
1541     while (*s != NULLCHAR) {
1542         while (*s == '\033') {
1543             while (*s != NULLCHAR && !isalpha(*s)) s++;
1544             if (*s != NULLCHAR) s++;
1545         }
1546         while (*s != NULLCHAR && *s != '\033') {
1547             *p++ = *s++;
1548         }
1549     }
1550     *p = NULLCHAR;
1551     return retbuf;
1552 }
1553
1554 char *variantNames[] = VARIANT_NAMES;
1555 char *
1556 VariantName(v)
1557      VariantClass v;
1558 {
1559     return variantNames[v];
1560 }
1561
1562
1563 /* Identify a variant from the strings the chess servers use or the
1564    PGN Variant tag names we use. */
1565 VariantClass
1566 StringToVariant(e)
1567      char *e;
1568 {
1569     char *p;
1570     int wnum = -1;
1571     VariantClass v = VariantNormal;
1572     int i, found = FALSE;
1573     char buf[MSG_SIZ];
1574
1575     if (!e) return v;
1576
1577     /* [HGM] skip over optional board-size prefixes */
1578     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580         while( *e++ != '_');
1581     }
1582
1583     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584       if (StrCaseStr(e, variantNames[i])) {
1585         v = (VariantClass) i;
1586         found = TRUE;
1587         break;
1588       }
1589     }
1590
1591     if (!found) {
1592       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593           || StrCaseStr(e, "wild/fr")
1594           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595         v = VariantFischeRandom;
1596       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597                  (i = 1, p = StrCaseStr(e, "w"))) {
1598         p += i;
1599         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1600         if (isdigit(*p)) {
1601           wnum = atoi(p);
1602         } else {
1603           wnum = -1;
1604         }
1605         switch (wnum) {
1606         case 0: /* FICS only, actually */
1607         case 1:
1608           /* Castling legal even if K starts on d-file */
1609           v = VariantWildCastle;
1610           break;
1611         case 2:
1612         case 3:
1613         case 4:
1614           /* Castling illegal even if K & R happen to start in
1615              normal positions. */
1616           v = VariantNoCastle;
1617           break;
1618         case 5:
1619         case 7:
1620         case 8:
1621         case 10:
1622         case 11:
1623         case 12:
1624         case 13:
1625         case 14:
1626         case 15:
1627         case 18:
1628         case 19:
1629           /* Castling legal iff K & R start in normal positions */
1630           v = VariantNormal;
1631           break;
1632         case 6:
1633         case 20:
1634         case 21:
1635           /* Special wilds for position setup; unclear what to do here */
1636           v = VariantLoadable;
1637           break;
1638         case 9:
1639           /* Bizarre ICC game */
1640           v = VariantTwoKings;
1641           break;
1642         case 16:
1643           v = VariantKriegspiel;
1644           break;
1645         case 17:
1646           v = VariantLosers;
1647           break;
1648         case 22:
1649           v = VariantFischeRandom;
1650           break;
1651         case 23:
1652           v = VariantCrazyhouse;
1653           break;
1654         case 24:
1655           v = VariantBughouse;
1656           break;
1657         case 25:
1658           v = Variant3Check;
1659           break;
1660         case 26:
1661           /* Not quite the same as FICS suicide! */
1662           v = VariantGiveaway;
1663           break;
1664         case 27:
1665           v = VariantAtomic;
1666           break;
1667         case 28:
1668           v = VariantShatranj;
1669           break;
1670
1671         /* Temporary names for future ICC types.  The name *will* change in
1672            the next xboard/WinBoard release after ICC defines it. */
1673         case 29:
1674           v = Variant29;
1675           break;
1676         case 30:
1677           v = Variant30;
1678           break;
1679         case 31:
1680           v = Variant31;
1681           break;
1682         case 32:
1683           v = Variant32;
1684           break;
1685         case 33:
1686           v = Variant33;
1687           break;
1688         case 34:
1689           v = Variant34;
1690           break;
1691         case 35:
1692           v = Variant35;
1693           break;
1694         case 36:
1695           v = Variant36;
1696           break;
1697         case 37:
1698           v = VariantShogi;
1699           break;
1700         case 38:
1701           v = VariantXiangqi;
1702           break;
1703         case 39:
1704           v = VariantCourier;
1705           break;
1706         case 40:
1707           v = VariantGothic;
1708           break;
1709         case 41:
1710           v = VariantCapablanca;
1711           break;
1712         case 42:
1713           v = VariantKnightmate;
1714           break;
1715         case 43:
1716           v = VariantFairy;
1717           break;
1718         case 44:
1719           v = VariantCylinder;
1720           break;
1721         case 45:
1722           v = VariantFalcon;
1723           break;
1724         case 46:
1725           v = VariantCapaRandom;
1726           break;
1727         case 47:
1728           v = VariantBerolina;
1729           break;
1730         case 48:
1731           v = VariantJanus;
1732           break;
1733         case 49:
1734           v = VariantSuper;
1735           break;
1736         case 50:
1737           v = VariantGreat;
1738           break;
1739         case -1:
1740           /* Found "wild" or "w" in the string but no number;
1741              must assume it's normal chess. */
1742           v = VariantNormal;
1743           break;
1744         default:
1745           sprintf(buf, _("Unknown wild type %d"), wnum);
1746           DisplayError(buf, 0);
1747           v = VariantUnknown;
1748           break;
1749         }
1750       }
1751     }
1752     if (appData.debugMode) {
1753       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754               e, wnum, VariantName(v));
1755     }
1756     return v;
1757 }
1758
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1761
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763    advance *index beyond it, and set leftover_start to the new value of
1764    *index; else return FALSE.  If pattern contains the character '*', it
1765    matches any sequence of characters not containing '\r', '\n', or the
1766    character following the '*' (if any), and the matched sequence(s) are
1767    copied into star_match.
1768    */
1769 int
1770 looking_at(buf, index, pattern)
1771      char *buf;
1772      int *index;
1773      char *pattern;
1774 {
1775     char *bufp = &buf[*index], *patternp = pattern;
1776     int star_count = 0;
1777     char *matchp = star_match[0];
1778
1779     for (;;) {
1780         if (*patternp == NULLCHAR) {
1781             *index = leftover_start = bufp - buf;
1782             *matchp = NULLCHAR;
1783             return TRUE;
1784         }
1785         if (*bufp == NULLCHAR) return FALSE;
1786         if (*patternp == '*') {
1787             if (*bufp == *(patternp + 1)) {
1788                 *matchp = NULLCHAR;
1789                 matchp = star_match[++star_count];
1790                 patternp += 2;
1791                 bufp++;
1792                 continue;
1793             } else if (*bufp == '\n' || *bufp == '\r') {
1794                 patternp++;
1795                 if (*patternp == NULLCHAR)
1796                   continue;
1797                 else
1798                   return FALSE;
1799             } else {
1800                 *matchp++ = *bufp++;
1801                 continue;
1802             }
1803         }
1804         if (*patternp != *bufp) return FALSE;
1805         patternp++;
1806         bufp++;
1807     }
1808 }
1809
1810 void
1811 SendToPlayer(data, length)
1812      char *data;
1813      int length;
1814 {
1815     int error, outCount;
1816     outCount = OutputToProcess(NoProc, data, length, &error);
1817     if (outCount < length) {
1818         DisplayFatalError(_("Error writing to display"), error, 1);
1819     }
1820 }
1821
1822 void
1823 PackHolding(packed, holding)
1824      char packed[];
1825      char *holding;
1826 {
1827     char *p = holding;
1828     char *q = packed;
1829     int runlength = 0;
1830     int curr = 9999;
1831     do {
1832         if (*p == curr) {
1833             runlength++;
1834         } else {
1835             switch (runlength) {
1836               case 0:
1837                 break;
1838               case 1:
1839                 *q++ = curr;
1840                 break;
1841               case 2:
1842                 *q++ = curr;
1843                 *q++ = curr;
1844                 break;
1845               default:
1846                 sprintf(q, "%d", runlength);
1847                 while (*q) q++;
1848                 *q++ = curr;
1849                 break;
1850             }
1851             runlength = 1;
1852             curr = *p;
1853         }
1854     } while (*p++);
1855     *q = NULLCHAR;
1856 }
1857
1858 /* Telnet protocol requests from the front end */
1859 void
1860 TelnetRequest(ddww, option)
1861      unsigned char ddww, option;
1862 {
1863     unsigned char msg[3];
1864     int outCount, outError;
1865
1866     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867
1868     if (appData.debugMode) {
1869         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1870         switch (ddww) {
1871           case TN_DO:
1872             ddwwStr = "DO";
1873             break;
1874           case TN_DONT:
1875             ddwwStr = "DONT";
1876             break;
1877           case TN_WILL:
1878             ddwwStr = "WILL";
1879             break;
1880           case TN_WONT:
1881             ddwwStr = "WONT";
1882             break;
1883           default:
1884             ddwwStr = buf1;
1885             sprintf(buf1, "%d", ddww);
1886             break;
1887         }
1888         switch (option) {
1889           case TN_ECHO:
1890             optionStr = "ECHO";
1891             break;
1892           default:
1893             optionStr = buf2;
1894             sprintf(buf2, "%d", option);
1895             break;
1896         }
1897         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1898     }
1899     msg[0] = TN_IAC;
1900     msg[1] = ddww;
1901     msg[2] = option;
1902     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903     if (outCount < 3) {
1904         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1905     }
1906 }
1907
1908 void
1909 DoEcho()
1910 {
1911     if (!appData.icsActive) return;
1912     TelnetRequest(TN_DO, TN_ECHO);
1913 }
1914
1915 void
1916 DontEcho()
1917 {
1918     if (!appData.icsActive) return;
1919     TelnetRequest(TN_DONT, TN_ECHO);
1920 }
1921
1922 void
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 {
1925     /* put the holdings sent to us by the server on the board holdings area */
1926     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1927     char p;
1928     ChessSquare piece;
1929
1930     if(gameInfo.holdingsWidth < 2)  return;
1931
1932     if( (int)lowestPiece >= BlackPawn ) {
1933         holdingsColumn = 0;
1934         countsColumn = 1;
1935         holdingsStartRow = BOARD_HEIGHT-1;
1936         direction = -1;
1937     } else {
1938         holdingsColumn = BOARD_WIDTH-1;
1939         countsColumn = BOARD_WIDTH-2;
1940         holdingsStartRow = 0;
1941         direction = 1;
1942     }
1943
1944     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945         board[i][holdingsColumn] = EmptySquare;
1946         board[i][countsColumn]   = (ChessSquare) 0;
1947     }
1948     while( (p=*holdings++) != NULLCHAR ) {
1949         piece = CharToPiece( ToUpper(p) );
1950         if(piece == EmptySquare) continue;
1951         /*j = (int) piece - (int) WhitePawn;*/
1952         j = PieceToNumber(piece);
1953         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954         if(j < 0) continue;               /* should not happen */
1955         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957         board[holdingsStartRow+j*direction][countsColumn]++;
1958     }
1959
1960 }
1961
1962
1963 void
1964 VariantSwitch(Board board, VariantClass newVariant)
1965 {
1966    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1969
1970    startedFromPositionFile = FALSE;
1971    if(gameInfo.variant == newVariant) return;
1972
1973    /* [HGM] This routine is called each time an assignment is made to
1974     * gameInfo.variant during a game, to make sure the board sizes
1975     * are set to match the new variant. If that means adding or deleting
1976     * holdings, we shift the playing board accordingly
1977     * This kludge is needed because in ICS observe mode, we get boards
1978     * of an ongoing game without knowing the variant, and learn about the
1979     * latter only later. This can be because of the move list we requested,
1980     * in which case the game history is refilled from the beginning anyway,
1981     * but also when receiving holdings of a crazyhouse game. In the latter
1982     * case we want to add those holdings to the already received position.
1983     */
1984
1985
1986   if (appData.debugMode) {
1987     fprintf(debugFP, "Switch board from %s to %s\n",
1988                VariantName(gameInfo.variant), VariantName(newVariant));
1989     setbuf(debugFP, NULL);
1990   }
1991     shuffleOpenings = 0;       /* [HGM] shuffle */
1992     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993     switch(newVariant) {
1994             case VariantShogi:
1995               newWidth = 9;  newHeight = 9;
1996               gameInfo.holdingsSize = 7;
1997             case VariantBughouse:
1998             case VariantCrazyhouse:
1999               newHoldingsWidth = 2; break;
2000             default:
2001               newHoldingsWidth = gameInfo.holdingsSize = 0;
2002     }
2003
2004     if(newWidth  != gameInfo.boardWidth  ||
2005        newHeight != gameInfo.boardHeight ||
2006        newHoldingsWidth != gameInfo.holdingsWidth ) {
2007
2008         /* shift position to new playing area, if needed */
2009         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010            for(i=0; i<BOARD_HEIGHT; i++)
2011                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013                                                      board[i][j];
2014            for(i=0; i<newHeight; i++) {
2015                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017            }
2018         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019            for(i=0; i<BOARD_HEIGHT; i++)
2020                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022                                                  board[i][j];
2023         }
2024
2025         gameInfo.boardWidth  = newWidth;
2026         gameInfo.boardHeight = newHeight;
2027         gameInfo.holdingsWidth = newHoldingsWidth;
2028         gameInfo.variant = newVariant;
2029         InitDrawingSizes(-2, 0);
2030
2031         /* [HGM] The following should definitely be solved in a better way */
2032 #if 0
2033         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035         saveEP = epStatus[0];
2036 #endif
2037         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2038 #if 0
2039         epStatus[0] = saveEP;
2040         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2042 #endif
2043     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2044
2045     forwardMostMove = oldForwardMostMove;
2046     backwardMostMove = oldBackwardMostMove;
2047     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2048 }
2049
2050 static int loggedOn = FALSE;
2051
2052 /*-- Game start info cache: --*/
2053 int gs_gamenum;
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2060
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2063
2064 void
2065 read_from_ics(isr, closure, data, count, error)
2066      InputSourceRef isr;
2067      VOIDSTAR closure;
2068      char *data;
2069      int count;
2070      int error;
2071 {
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2081
2082     static int started = STARTED_NONE;
2083     static char parse[20000];
2084     static int parse_pos = 0;
2085     static char buf[BUF_SIZE + 1];
2086     static int firstTime = TRUE, intfSet = FALSE;
2087     static ColorClass prevColor = ColorNormal;
2088     static int savingComment = FALSE;
2089     char str[500];
2090     int i, oldi;
2091     int buf_len;
2092     int next_out;
2093     int tkind;
2094     int backup;    /* [DM] For zippy color lines */
2095     char *p;
2096     char talker[MSG_SIZ]; // [HGM] chat
2097     int channel;
2098
2099     if (appData.debugMode) {
2100       if (!error) {
2101         fprintf(debugFP, "<ICS: ");
2102         show_bytes(debugFP, data, count);
2103         fprintf(debugFP, "\n");
2104       }
2105     }
2106
2107     if (appData.debugMode) { int f = forwardMostMove;
2108         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2110     }
2111     if (count > 0) {
2112         /* If last read ended with a partial line that we couldn't parse,
2113            prepend it to the new read and try again. */
2114         if (leftover_len > 0) {
2115             for (i=0; i<leftover_len; i++)
2116               buf[i] = buf[leftover_start + i];
2117         }
2118
2119         /* Copy in new characters, removing nulls and \r's */
2120         buf_len = leftover_len;
2121         for (i = 0; i < count; i++) {
2122             if (data[i] != NULLCHAR && data[i] != '\r')
2123               buf[buf_len++] = data[i];
2124             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2125                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2126                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2128             }
2129         }
2130
2131         buf[buf_len] = NULLCHAR;
2132         next_out = leftover_len;
2133         leftover_start = 0;
2134
2135         i = 0;
2136         while (i < buf_len) {
2137             /* Deal with part of the TELNET option negotiation
2138                protocol.  We refuse to do anything beyond the
2139                defaults, except that we allow the WILL ECHO option,
2140                which ICS uses to turn off password echoing when we are
2141                directly connected to it.  We reject this option
2142                if localLineEditing mode is on (always on in xboard)
2143                and we are talking to port 23, which might be a real
2144                telnet server that will try to keep WILL ECHO on permanently.
2145              */
2146             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148                 unsigned char option;
2149                 oldi = i;
2150                 switch ((unsigned char) buf[++i]) {
2151                   case TN_WILL:
2152                     if (appData.debugMode)
2153                       fprintf(debugFP, "\n<WILL ");
2154                     switch (option = (unsigned char) buf[++i]) {
2155                       case TN_ECHO:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "ECHO ");
2158                         /* Reply only if this is a change, according
2159                            to the protocol rules. */
2160                         if (remoteEchoOption) break;
2161                         if (appData.localLineEditing &&
2162                             atoi(appData.icsPort) == TN_PORT) {
2163                             TelnetRequest(TN_DONT, TN_ECHO);
2164                         } else {
2165                             EchoOff();
2166                             TelnetRequest(TN_DO, TN_ECHO);
2167                             remoteEchoOption = TRUE;
2168                         }
2169                         break;
2170                       default:
2171                         if (appData.debugMode)
2172                           fprintf(debugFP, "%d ", option);
2173                         /* Whatever this is, we don't want it. */
2174                         TelnetRequest(TN_DONT, option);
2175                         break;
2176                     }
2177                     break;
2178                   case TN_WONT:
2179                     if (appData.debugMode)
2180                       fprintf(debugFP, "\n<WONT ");
2181                     switch (option = (unsigned char) buf[++i]) {
2182                       case TN_ECHO:
2183                         if (appData.debugMode)
2184                           fprintf(debugFP, "ECHO ");
2185                         /* Reply only if this is a change, according
2186                            to the protocol rules. */
2187                         if (!remoteEchoOption) break;
2188                         EchoOn();
2189                         TelnetRequest(TN_DONT, TN_ECHO);
2190                         remoteEchoOption = FALSE;
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", (unsigned char) option);
2195                         /* Whatever this is, it must already be turned
2196                            off, because we never agree to turn on
2197                            anything non-default, so according to the
2198                            protocol rules, we don't reply. */
2199                         break;
2200                     }
2201                     break;
2202                   case TN_DO:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<DO ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       default:
2207                         /* Whatever this is, we refuse to do it. */
2208                         if (appData.debugMode)
2209                           fprintf(debugFP, "%d ", option);
2210                         TelnetRequest(TN_WONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_DONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<DONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       default:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "%d ", option);
2221                         /* Whatever this is, we are already not doing
2222                            it, because we never agree to do anything
2223                            non-default, so according to the protocol
2224                            rules, we don't reply. */
2225                         break;
2226                     }
2227                     break;
2228                   case TN_IAC:
2229                     if (appData.debugMode)
2230                       fprintf(debugFP, "\n<IAC ");
2231                     /* Doubled IAC; pass it through */
2232                     i--;
2233                     break;
2234                   default:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237                     /* Drop all other telnet commands on the floor */
2238                     break;
2239                 }
2240                 if (oldi > next_out)
2241                   SendToPlayer(&buf[next_out], oldi - next_out);
2242                 if (++i > next_out)
2243                   next_out = i;
2244                 continue;
2245             }
2246
2247             /* OK, this at least will *usually* work */
2248             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2249                 loggedOn = TRUE;
2250             }
2251
2252             if (loggedOn && !intfSet) {
2253                 if (ics_type == ICS_ICC) {
2254                   sprintf(str,
2255                           "/set-quietly interface %s\n/set-quietly style 12\n",
2256                           programVersion);
2257
2258                 } else if (ics_type == ICS_CHESSNET) {
2259                   sprintf(str, "/style 12\n");
2260                 } else {
2261                   strcpy(str, "alias $ @\n$set interface ");
2262                   strcat(str, programVersion);
2263                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2264 #ifdef WIN32
2265                   strcat(str, "$iset nohighlight 1\n");
2266 #endif
2267                   strcat(str, "$iset lock 1\n$style 12\n");
2268                 }
2269                 SendToICS(str);
2270                 intfSet = TRUE;
2271             }
2272
2273             if (started == STARTED_COMMENT) {
2274                 /* Accumulate characters in comment */
2275                 parse[parse_pos++] = buf[i];
2276                 if (buf[i] == '\n') {
2277                     parse[parse_pos] = NULLCHAR;
2278                     if(chattingPartner>=0) {
2279                         char mess[MSG_SIZ];
2280                         sprintf(mess, "%s%s", talker, parse);
2281                         OutputChatMessage(chattingPartner, mess);
2282                         chattingPartner = -1;
2283                     } else
2284                     if(!suppressKibitz) // [HGM] kibitz
2285                         AppendComment(forwardMostMove, StripHighlight(parse));
2286                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287                         int nrDigit = 0, nrAlph = 0, i;
2288                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290                         parse[parse_pos] = NULLCHAR;
2291                         // try to be smart: if it does not look like search info, it should go to
2292                         // ICS interaction window after all, not to engine-output window.
2293                         for(i=0; i<parse_pos; i++) { // count letters and digits
2294                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2296                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2297                         }
2298                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299                             int depth=0; float score;
2300                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302                                 pvInfoList[forwardMostMove-1].depth = depth;
2303                                 pvInfoList[forwardMostMove-1].score = 100*score;
2304                             }
2305                             OutputKibitz(suppressKibitz, parse);
2306                         } else {
2307                             char tmp[MSG_SIZ];
2308                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309                             SendToPlayer(tmp, strlen(tmp));
2310                         }
2311                     }
2312                     started = STARTED_NONE;
2313                 } else {
2314                     /* Don't match patterns against characters in chatter */
2315                     i++;
2316                     continue;
2317                 }
2318             }
2319             if (started == STARTED_CHATTER) {
2320                 if (buf[i] != '\n') {
2321                     /* Don't match patterns against characters in chatter */
2322                     i++;
2323                     continue;
2324                 }
2325                 started = STARTED_NONE;
2326             }
2327
2328             /* Kludge to deal with rcmd protocol */
2329             if (firstTime && looking_at(buf, &i, "\001*")) {
2330                 DisplayFatalError(&buf[1], 0, 1);
2331                 continue;
2332             } else {
2333                 firstTime = FALSE;
2334             }
2335
2336             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2337                 ics_type = ICS_ICC;
2338                 ics_prefix = "/";
2339                 if (appData.debugMode)
2340                   fprintf(debugFP, "ics_type %d\n", ics_type);
2341                 continue;
2342             }
2343             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344                 ics_type = ICS_FICS;
2345                 ics_prefix = "$";
2346                 if (appData.debugMode)
2347                   fprintf(debugFP, "ics_type %d\n", ics_type);
2348                 continue;
2349             }
2350             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351                 ics_type = ICS_CHESSNET;
2352                 ics_prefix = "/";
2353                 if (appData.debugMode)
2354                   fprintf(debugFP, "ics_type %d\n", ics_type);
2355                 continue;
2356             }
2357
2358             if (!loggedOn &&
2359                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2361                  looking_at(buf, &i, "will be \"*\""))) {
2362               strcpy(ics_handle, star_match[0]);
2363               continue;
2364             }
2365
2366             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2367               char buf[MSG_SIZ];
2368               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369               DisplayIcsInteractionTitle(buf);
2370               have_set_title = TRUE;
2371             }
2372
2373             /* skip finger notes */
2374             if (started == STARTED_NONE &&
2375                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376                  (buf[i] == '1' && buf[i+1] == '0')) &&
2377                 buf[i+2] == ':' && buf[i+3] == ' ') {
2378               started = STARTED_CHATTER;
2379               i += 3;
2380               continue;
2381             }
2382
2383             /* skip formula vars */
2384             if (started == STARTED_NONE &&
2385                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386               started = STARTED_CHATTER;
2387               i += 3;
2388               continue;
2389             }
2390
2391             oldi = i;
2392             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393             if (appData.autoKibitz && started == STARTED_NONE &&
2394                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2395                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396                 if(looking_at(buf, &i, "* kibitzes: ") &&
2397                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2398                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2399                         suppressKibitz = TRUE;
2400                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401                                 && (gameMode == IcsPlayingWhite)) ||
2402                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2404                             started = STARTED_CHATTER; // own kibitz we simply discard
2405                         else {
2406                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407                             parse_pos = 0; parse[0] = NULLCHAR;
2408                             savingComment = TRUE;
2409                             suppressKibitz = gameMode != IcsObserving ? 2 :
2410                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2411                         }
2412                         continue;
2413                 } else
2414                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415                     started = STARTED_CHATTER;
2416                     suppressKibitz = TRUE;
2417                 }
2418             } // [HGM] kibitz: end of patch
2419
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2421
2422             // [HGM] chat: intercept tells by users for which we have an open chat window
2423             channel = -1;
2424             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2425                                            looking_at(buf, &i, "* whispers:") ||
2426                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2428                 int p;
2429                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430                 chattingPartner = -1;
2431
2432                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433                 for(p=0; p<MAX_CHAT; p++) {
2434                     if(channel == atoi(chatPartner[p])) {
2435                     talker[0] = '['; strcat(talker, "]");
2436                     chattingPartner = p; break;
2437                     }
2438                 } else
2439                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440                 for(p=0; p<MAX_CHAT; p++) {
2441                     if(!strcmp("WHISPER", chatPartner[p])) {
2442                         talker[0] = '['; strcat(talker, "]");
2443                         chattingPartner = p; break;
2444                     }
2445                 }
2446                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2448                     talker[0] = 0;
2449                     chattingPartner = p; break;
2450                 }
2451                 if(chattingPartner<0) i = oldi; else {
2452                     started = STARTED_COMMENT;
2453                     parse_pos = 0; parse[0] = NULLCHAR;
2454                     savingComment = TRUE;
2455                     suppressKibitz = TRUE;
2456                 }
2457             } // [HGM] chat: end of patch
2458
2459             if (appData.zippyTalk || appData.zippyPlay) {
2460                 /* [DM] Backup address for color zippy lines */
2461                 backup = i;
2462 #if ZIPPY
2463        #ifdef WIN32
2464                if (loggedOn == TRUE)
2465                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2467        #else
2468                 if (ZippyControl(buf, &i) ||
2469                     ZippyConverse(buf, &i) ||
2470                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2471                       loggedOn = TRUE;
2472                       if (!appData.colorize) continue;
2473                 }
2474        #endif
2475 #endif
2476             } // [DM] 'else { ' deleted
2477                 if (
2478                     /* Regular tells and says */
2479                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2481                     looking_at(buf, &i, "* says: ") ||
2482                     /* Don't color "message" or "messages" output */
2483                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484                     looking_at(buf, &i, "*. * at *:*: ") ||
2485                     looking_at(buf, &i, "--* (*:*): ") ||
2486                     /* Message notifications (same color as tells) */
2487                     looking_at(buf, &i, "* has left a message ") ||
2488                     looking_at(buf, &i, "* just sent you a message:\n") ||
2489                     /* Whispers and kibitzes */
2490                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491                     looking_at(buf, &i, "* kibitzes: ") ||
2492                     /* Channel tells */
2493                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2494
2495                   if (tkind == 1 && strchr(star_match[0], ':')) {
2496                       /* Avoid "tells you:" spoofs in channels */
2497                      tkind = 3;
2498                   }
2499                   if (star_match[0][0] == NULLCHAR ||
2500                       strchr(star_match[0], ' ') ||
2501                       (tkind == 3 && strchr(star_match[1], ' '))) {
2502                     /* Reject bogus matches */
2503                     i = oldi;
2504                   } else {
2505                     if (appData.colorize) {
2506                       if (oldi > next_out) {
2507                         SendToPlayer(&buf[next_out], oldi - next_out);
2508                         next_out = oldi;
2509                       }
2510                       switch (tkind) {
2511                       case 1:
2512                         Colorize(ColorTell, FALSE);
2513                         curColor = ColorTell;
2514                         break;
2515                       case 2:
2516                         Colorize(ColorKibitz, FALSE);
2517                         curColor = ColorKibitz;
2518                         break;
2519                       case 3:
2520                         p = strrchr(star_match[1], '(');
2521                         if (p == NULL) {
2522                           p = star_match[1];
2523                         } else {
2524                           p++;
2525                         }
2526                         if (atoi(p) == 1) {
2527                           Colorize(ColorChannel1, FALSE);
2528                           curColor = ColorChannel1;
2529                         } else {
2530                           Colorize(ColorChannel, FALSE);
2531                           curColor = ColorChannel;
2532                         }
2533                         break;
2534                       case 5:
2535                         curColor = ColorNormal;
2536                         break;
2537                       }
2538                     }
2539                     if (started == STARTED_NONE && appData.autoComment &&
2540                         (gameMode == IcsObserving ||
2541                          gameMode == IcsPlayingWhite ||
2542                          gameMode == IcsPlayingBlack)) {
2543                       parse_pos = i - oldi;
2544                       memcpy(parse, &buf[oldi], parse_pos);
2545                       parse[parse_pos] = NULLCHAR;
2546                       started = STARTED_COMMENT;
2547                       savingComment = TRUE;
2548                     } else {
2549                       started = STARTED_CHATTER;
2550                       savingComment = FALSE;
2551                     }
2552                     loggedOn = TRUE;
2553                     continue;
2554                   }
2555                 }
2556
2557                 if (looking_at(buf, &i, "* s-shouts: ") ||
2558                     looking_at(buf, &i, "* c-shouts: ")) {
2559                     if (appData.colorize) {
2560                         if (oldi > next_out) {
2561                             SendToPlayer(&buf[next_out], oldi - next_out);
2562                             next_out = oldi;
2563                         }
2564                         Colorize(ColorSShout, FALSE);
2565                         curColor = ColorSShout;
2566                     }
2567                     loggedOn = TRUE;
2568                     started = STARTED_CHATTER;
2569                     continue;
2570                 }
2571
2572                 if (looking_at(buf, &i, "--->")) {
2573                     loggedOn = TRUE;
2574                     continue;
2575                 }
2576
2577                 if (looking_at(buf, &i, "* shouts: ") ||
2578                     looking_at(buf, &i, "--> ")) {
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorShout, FALSE);
2585                         curColor = ColorShout;
2586                     }
2587                     loggedOn = TRUE;
2588                     started = STARTED_CHATTER;
2589                     continue;
2590                 }
2591
2592                 if (looking_at( buf, &i, "Challenge:")) {
2593                     if (appData.colorize) {
2594                         if (oldi > next_out) {
2595                             SendToPlayer(&buf[next_out], oldi - next_out);
2596                             next_out = oldi;
2597                         }
2598                         Colorize(ColorChallenge, FALSE);
2599                         curColor = ColorChallenge;
2600                     }
2601                     loggedOn = TRUE;
2602                     continue;
2603                 }
2604
2605                 if (looking_at(buf, &i, "* offers you") ||
2606                     looking_at(buf, &i, "* offers to be") ||
2607                     looking_at(buf, &i, "* would like to") ||
2608                     looking_at(buf, &i, "* requests to") ||
2609                     looking_at(buf, &i, "Your opponent offers") ||
2610                     looking_at(buf, &i, "Your opponent requests")) {
2611
2612                     if (appData.colorize) {
2613                         if (oldi > next_out) {
2614                             SendToPlayer(&buf[next_out], oldi - next_out);
2615                             next_out = oldi;
2616                         }
2617                         Colorize(ColorRequest, FALSE);
2618                         curColor = ColorRequest;
2619                     }
2620                     continue;
2621                 }
2622
2623                 if (looking_at(buf, &i, "* (*) seeking")) {
2624                     if (appData.colorize) {
2625                         if (oldi > next_out) {
2626                             SendToPlayer(&buf[next_out], oldi - next_out);
2627                             next_out = oldi;
2628                         }
2629                         Colorize(ColorSeek, FALSE);
2630                         curColor = ColorSeek;
2631                     }
2632                     continue;
2633             }
2634
2635             if (looking_at(buf, &i, "\\   ")) {
2636                 if (prevColor != ColorNormal) {
2637                     if (oldi > next_out) {
2638                         SendToPlayer(&buf[next_out], oldi - next_out);
2639                         next_out = oldi;
2640                     }
2641                     Colorize(prevColor, TRUE);
2642                     curColor = prevColor;
2643                 }
2644                 if (savingComment) {
2645                     parse_pos = i - oldi;
2646                     memcpy(parse, &buf[oldi], parse_pos);
2647                     parse[parse_pos] = NULLCHAR;
2648                     started = STARTED_COMMENT;
2649                 } else {
2650                     started = STARTED_CHATTER;
2651                 }
2652                 continue;
2653             }
2654
2655             if (looking_at(buf, &i, "Black Strength :") ||
2656                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657                 looking_at(buf, &i, "<10>") ||
2658                 looking_at(buf, &i, "#@#")) {
2659                 /* Wrong board style */
2660                 loggedOn = TRUE;
2661                 SendToICS(ics_prefix);
2662                 SendToICS("set style 12\n");
2663                 SendToICS(ics_prefix);
2664                 SendToICS("refresh\n");
2665                 continue;
2666             }
2667
2668             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2669                 ICSInitScript();
2670                 have_sent_ICS_logon = 1;
2671                 continue;
2672             }
2673
2674             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2675                 (looking_at(buf, &i, "\n<12> ") ||
2676                  looking_at(buf, &i, "<12> "))) {
2677                 loggedOn = TRUE;
2678                 if (oldi > next_out) {
2679                     SendToPlayer(&buf[next_out], oldi - next_out);
2680                 }
2681                 next_out = i;
2682                 started = STARTED_BOARD;
2683                 parse_pos = 0;
2684                 continue;
2685             }
2686
2687             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688                 looking_at(buf, &i, "<b1> ")) {
2689                 if (oldi > next_out) {
2690                     SendToPlayer(&buf[next_out], oldi - next_out);
2691                 }
2692                 next_out = i;
2693                 started = STARTED_HOLDINGS;
2694                 parse_pos = 0;
2695                 continue;
2696             }
2697
2698             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2699                 loggedOn = TRUE;
2700                 /* Header for a move list -- first line */
2701
2702                 switch (ics_getting_history) {
2703                   case H_FALSE:
2704                     switch (gameMode) {
2705                       case IcsIdle:
2706                       case BeginningOfGame:
2707                         /* User typed "moves" or "oldmoves" while we
2708                            were idle.  Pretend we asked for these
2709                            moves and soak them up so user can step
2710                            through them and/or save them.
2711                            */
2712                         Reset(FALSE, TRUE);
2713                         gameMode = IcsObserving;
2714                         ModeHighlight();
2715                         ics_gamenum = -1;
2716                         ics_getting_history = H_GOT_UNREQ_HEADER;
2717                         break;
2718                       case EditGame: /*?*/
2719                       case EditPosition: /*?*/
2720                         /* Should above feature work in these modes too? */
2721                         /* For now it doesn't */
2722                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2723                         break;
2724                       default:
2725                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2726                         break;
2727                     }
2728                     break;
2729                   case H_REQUESTED:
2730                     /* Is this the right one? */
2731                     if (gameInfo.white && gameInfo.black &&
2732                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2733                         strcmp(gameInfo.black, star_match[2]) == 0) {
2734                         /* All is well */
2735                         ics_getting_history = H_GOT_REQ_HEADER;
2736                     }
2737                     break;
2738                   case H_GOT_REQ_HEADER:
2739                   case H_GOT_UNREQ_HEADER:
2740                   case H_GOT_UNWANTED_HEADER:
2741                   case H_GETTING_MOVES:
2742                     /* Should not happen */
2743                     DisplayError(_("Error gathering move list: two headers"), 0);
2744                     ics_getting_history = H_FALSE;
2745                     break;
2746                 }
2747
2748                 /* Save player ratings into gameInfo if needed */
2749                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751                     (gameInfo.whiteRating == -1 ||
2752                      gameInfo.blackRating == -1)) {
2753
2754                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2755                     gameInfo.blackRating = string_to_rating(star_match[3]);
2756                     if (appData.debugMode)
2757                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2758                               gameInfo.whiteRating, gameInfo.blackRating);
2759                 }
2760                 continue;
2761             }
2762
2763             if (looking_at(buf, &i,
2764               "* * match, initial time: * minute*, increment: * second")) {
2765                 /* Header for a move list -- second line */
2766                 /* Initial board will follow if this is a wild game */
2767                 if (gameInfo.event != NULL) free(gameInfo.event);
2768                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769                 gameInfo.event = StrSave(str);
2770                 /* [HGM] we switched variant. Translate boards if needed. */
2771                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2772                 continue;
2773             }
2774
2775             if (looking_at(buf, &i, "Move  ")) {
2776                 /* Beginning of a move list */
2777                 switch (ics_getting_history) {
2778                   case H_FALSE:
2779                     /* Normally should not happen */
2780                     /* Maybe user hit reset while we were parsing */
2781                     break;
2782                   case H_REQUESTED:
2783                     /* Happens if we are ignoring a move list that is not
2784                      * the one we just requested.  Common if the user
2785                      * tries to observe two games without turning off
2786                      * getMoveList */
2787                     break;
2788                   case H_GETTING_MOVES:
2789                     /* Should not happen */
2790                     DisplayError(_("Error gathering move list: nested"), 0);
2791                     ics_getting_history = H_FALSE;
2792                     break;
2793                   case H_GOT_REQ_HEADER:
2794                     ics_getting_history = H_GETTING_MOVES;
2795                     started = STARTED_MOVES;
2796                     parse_pos = 0;
2797                     if (oldi > next_out) {
2798                         SendToPlayer(&buf[next_out], oldi - next_out);
2799                     }
2800                     break;
2801                   case H_GOT_UNREQ_HEADER:
2802                     ics_getting_history = H_GETTING_MOVES;
2803                     started = STARTED_MOVES_NOHIDE;
2804                     parse_pos = 0;
2805                     break;
2806                   case H_GOT_UNWANTED_HEADER:
2807                     ics_getting_history = H_FALSE;
2808                     break;
2809                 }
2810                 continue;
2811             }
2812
2813             if (looking_at(buf, &i, "% ") ||
2814                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816                 savingComment = FALSE;
2817                 switch (started) {
2818                   case STARTED_MOVES:
2819                   case STARTED_MOVES_NOHIDE:
2820                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821                     parse[parse_pos + i - oldi] = NULLCHAR;
2822                     ParseGameHistory(parse);
2823 #if ZIPPY
2824                     if (appData.zippyPlay && first.initDone) {
2825                         FeedMovesToProgram(&first, forwardMostMove);
2826                         if (gameMode == IcsPlayingWhite) {
2827                             if (WhiteOnMove(forwardMostMove)) {
2828                                 if (first.sendTime) {
2829                                   if (first.useColors) {
2830                                     SendToProgram("black\n", &first);
2831                                   }
2832                                   SendTimeRemaining(&first, TRUE);
2833                                 }
2834 #if 0
2835                                 if (first.useColors) {
2836                                   SendToProgram("white\ngo\n", &first);
2837                                 } else {
2838                                   SendToProgram("go\n", &first);
2839                                 }
2840 #else
2841                                 if (first.useColors) {
2842                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2843                                 }
2844                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2845 #endif
2846                                 first.maybeThinking = TRUE;
2847                             } else {
2848                                 if (first.usePlayother) {
2849                                   if (first.sendTime) {
2850                                     SendTimeRemaining(&first, TRUE);
2851                                   }
2852                                   SendToProgram("playother\n", &first);
2853                                   firstMove = FALSE;
2854                                 } else {
2855                                   firstMove = TRUE;
2856                                 }
2857                             }
2858                         } else if (gameMode == IcsPlayingBlack) {
2859                             if (!WhiteOnMove(forwardMostMove)) {
2860                                 if (first.sendTime) {
2861                                   if (first.useColors) {
2862                                     SendToProgram("white\n", &first);
2863                                   }
2864                                   SendTimeRemaining(&first, FALSE);
2865                                 }
2866 #if 0
2867                                 if (first.useColors) {
2868                                   SendToProgram("black\ngo\n", &first);
2869                                 } else {
2870                                   SendToProgram("go\n", &first);
2871                                 }
2872 #else
2873                                 if (first.useColors) {
2874                                   SendToProgram("black\n", &first);
2875                                 }
2876                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2877 #endif
2878                                 first.maybeThinking = TRUE;
2879                             } else {
2880                                 if (first.usePlayother) {
2881                                   if (first.sendTime) {
2882                                     SendTimeRemaining(&first, FALSE);
2883                                   }
2884                                   SendToProgram("playother\n", &first);
2885                                   firstMove = FALSE;
2886                                 } else {
2887                                   firstMove = TRUE;
2888                                 }
2889                             }
2890                         }
2891                     }
2892 #endif
2893                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2894                         /* Moves came from oldmoves or moves command
2895                            while we weren't doing anything else.
2896                            */
2897                         currentMove = forwardMostMove;
2898                         ClearHighlights();/*!!could figure this out*/
2899                         flipView = appData.flipView;
2900                         DrawPosition(FALSE, boards[currentMove]);
2901                         DisplayBothClocks();
2902                         sprintf(str, "%s vs. %s",
2903                                 gameInfo.white, gameInfo.black);
2904                         DisplayTitle(str);
2905                         gameMode = IcsIdle;
2906                     } else {
2907                         /* Moves were history of an active game */
2908                         if (gameInfo.resultDetails != NULL) {
2909                             free(gameInfo.resultDetails);
2910                             gameInfo.resultDetails = NULL;
2911                         }
2912                     }
2913                     HistorySet(parseList, backwardMostMove,
2914                                forwardMostMove, currentMove-1);
2915                     DisplayMove(currentMove - 1);
2916                     if (started == STARTED_MOVES) next_out = i;
2917                     started = STARTED_NONE;
2918                     ics_getting_history = H_FALSE;
2919                     break;
2920
2921                   case STARTED_OBSERVE:
2922                     started = STARTED_NONE;
2923                     SendToICS(ics_prefix);
2924                     SendToICS("refresh\n");
2925                     break;
2926
2927                   default:
2928                     break;
2929                 }
2930                 if(bookHit) { // [HGM] book: simulate book reply
2931                     static char bookMove[MSG_SIZ]; // a bit generous?
2932
2933                     programStats.nodes = programStats.depth = programStats.time =
2934                     programStats.score = programStats.got_only_move = 0;
2935                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2936
2937                     strcpy(bookMove, "move ");
2938                     strcat(bookMove, bookHit);
2939                     HandleMachineMove(bookMove, &first);
2940                 }
2941                 continue;
2942             }
2943
2944             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945                  started == STARTED_HOLDINGS ||
2946                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947                 /* Accumulate characters in move list or board */
2948                 parse[parse_pos++] = buf[i];
2949             }
2950
2951             /* Start of game messages.  Mostly we detect start of game
2952                when the first board image arrives.  On some versions
2953                of the ICS, though, we need to do a "refresh" after starting
2954                to observe in order to get the current board right away. */
2955             if (looking_at(buf, &i, "Adding game * to observation list")) {
2956                 started = STARTED_OBSERVE;
2957                 continue;
2958             }
2959
2960             /* Handle auto-observe */
2961             if (appData.autoObserve &&
2962                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2964                 char *player;
2965                 /* Choose the player that was highlighted, if any. */
2966                 if (star_match[0][0] == '\033' ||
2967                     star_match[1][0] != '\033') {
2968                     player = star_match[0];
2969                 } else {
2970                     player = star_match[2];
2971                 }
2972                 sprintf(str, "%sobserve %s\n",
2973                         ics_prefix, StripHighlightAndTitle(player));
2974                 SendToICS(str);
2975
2976                 /* Save ratings from notify string */
2977                 strcpy(player1Name, star_match[0]);
2978                 player1Rating = string_to_rating(star_match[1]);
2979                 strcpy(player2Name, star_match[2]);
2980                 player2Rating = string_to_rating(star_match[3]);
2981
2982                 if (appData.debugMode)
2983                   fprintf(debugFP,
2984                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2985                           player1Name, player1Rating,
2986                           player2Name, player2Rating);
2987
2988                 continue;
2989             }
2990
2991             /* Deal with automatic examine mode after a game,
2992                and with IcsObserving -> IcsExamining transition */
2993             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994                 looking_at(buf, &i, "has made you an examiner of game *")) {
2995
2996                 int gamenum = atoi(star_match[0]);
2997                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998                     gamenum == ics_gamenum) {
2999                     /* We were already playing or observing this game;
3000                        no need to refetch history */
3001                     gameMode = IcsExamining;
3002                     if (pausing) {
3003                         pauseExamForwardMostMove = forwardMostMove;
3004                     } else if (currentMove < forwardMostMove) {
3005                         ForwardInner(forwardMostMove);
3006                     }
3007                 } else {
3008                     /* I don't think this case really can happen */
3009                     SendToICS(ics_prefix);
3010                     SendToICS("refresh\n");
3011                 }
3012                 continue;
3013             }
3014
3015             /* Error messages */
3016 //          if (ics_user_moved) {
3017             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018                 if (looking_at(buf, &i, "Illegal move") ||
3019                     looking_at(buf, &i, "Not a legal move") ||
3020                     looking_at(buf, &i, "Your king is in check") ||
3021                     looking_at(buf, &i, "It isn't your turn") ||
3022                     looking_at(buf, &i, "It is not your move")) {
3023                     /* Illegal move */
3024                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025                         currentMove = --forwardMostMove;
3026                         DisplayMove(currentMove - 1); /* before DMError */
3027                         DrawPosition(FALSE, boards[currentMove]);
3028                         SwitchClocks();
3029                         DisplayBothClocks();
3030                     }
3031                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3032                     ics_user_moved = 0;
3033                     continue;
3034                 }
3035             }
3036
3037             if (looking_at(buf, &i, "still have time") ||
3038                 looking_at(buf, &i, "not out of time") ||
3039                 looking_at(buf, &i, "either player is out of time") ||
3040                 looking_at(buf, &i, "has timeseal; checking")) {
3041                 /* We must have called his flag a little too soon */
3042                 whiteFlag = blackFlag = FALSE;
3043                 continue;
3044             }
3045
3046             if (looking_at(buf, &i, "added * seconds to") ||
3047                 looking_at(buf, &i, "seconds were added to")) {
3048                 /* Update the clocks */
3049                 SendToICS(ics_prefix);
3050                 SendToICS("refresh\n");
3051                 continue;
3052             }
3053
3054             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055                 ics_clock_paused = TRUE;
3056                 StopClocks();
3057                 continue;
3058             }
3059
3060             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061                 ics_clock_paused = FALSE;
3062                 StartClocks();
3063                 continue;
3064             }
3065
3066             /* Grab player ratings from the Creating: message.
3067                Note we have to check for the special case when
3068                the ICS inserts things like [white] or [black]. */
3069             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3071                 /* star_matches:
3072                    0    player 1 name (not necessarily white)
3073                    1    player 1 rating
3074                    2    empty, white, or black (IGNORED)
3075                    3    player 2 name (not necessarily black)
3076                    4    player 2 rating
3077
3078                    The names/ratings are sorted out when the game
3079                    actually starts (below).
3080                 */
3081                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082                 player1Rating = string_to_rating(star_match[1]);
3083                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084                 player2Rating = string_to_rating(star_match[4]);
3085
3086                 if (appData.debugMode)
3087                   fprintf(debugFP,
3088                           "Ratings from 'Creating:' %s %d, %s %d\n",
3089                           player1Name, player1Rating,
3090                           player2Name, player2Rating);
3091
3092                 continue;
3093             }
3094
3095             /* Improved generic start/end-of-game messages */
3096             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098                 /* If tkind == 0: */
3099                 /* star_match[0] is the game number */
3100                 /*           [1] is the white player's name */
3101                 /*           [2] is the black player's name */
3102                 /* For end-of-game: */
3103                 /*           [3] is the reason for the game end */
3104                 /*           [4] is a PGN end game-token, preceded by " " */
3105                 /* For start-of-game: */
3106                 /*           [3] begins with "Creating" or "Continuing" */
3107                 /*           [4] is " *" or empty (don't care). */
3108                 int gamenum = atoi(star_match[0]);
3109                 char *whitename, *blackname, *why, *endtoken;
3110                 ChessMove endtype = (ChessMove) 0;
3111
3112                 if (tkind == 0) {
3113                   whitename = star_match[1];
3114                   blackname = star_match[2];
3115                   why = star_match[3];
3116                   endtoken = star_match[4];
3117                 } else {
3118                   whitename = star_match[1];
3119                   blackname = star_match[3];
3120                   why = star_match[5];
3121                   endtoken = star_match[6];
3122                 }
3123
3124                 /* Game start messages */
3125                 if (strncmp(why, "Creating ", 9) == 0 ||
3126                     strncmp(why, "Continuing ", 11) == 0) {
3127                     gs_gamenum = gamenum;
3128                     strcpy(gs_kind, strchr(why, ' ') + 1);
3129 #if ZIPPY
3130                     if (appData.zippyPlay) {
3131                         ZippyGameStart(whitename, blackname);
3132                     }
3133 #endif /*ZIPPY*/
3134                     continue;
3135                 }
3136
3137                 /* Game end messages */
3138                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139                     ics_gamenum != gamenum) {
3140                     continue;
3141                 }
3142                 while (endtoken[0] == ' ') endtoken++;
3143                 switch (endtoken[0]) {
3144                   case '*':
3145                   default:
3146                     endtype = GameUnfinished;
3147                     break;
3148                   case '0':
3149                     endtype = BlackWins;
3150                     break;
3151                   case '1':
3152                     if (endtoken[1] == '/')
3153                       endtype = GameIsDrawn;
3154                     else
3155                       endtype = WhiteWins;
3156                     break;
3157                 }
3158                 GameEnds(endtype, why, GE_ICS);
3159 #if ZIPPY
3160                 if (appData.zippyPlay && first.initDone) {
3161                     ZippyGameEnd(endtype, why);
3162                     if (first.pr == NULL) {
3163                       /* Start the next process early so that we'll
3164                          be ready for the next challenge */
3165                       StartChessProgram(&first);
3166                     }
3167                     /* Send "new" early, in case this command takes
3168                        a long time to finish, so that we'll be ready
3169                        for the next challenge. */
3170                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3171                     Reset(TRUE, TRUE);
3172                 }
3173 #endif /*ZIPPY*/
3174                 continue;
3175             }
3176
3177             if (looking_at(buf, &i, "Removing game * from observation") ||
3178                 looking_at(buf, &i, "no longer observing game *") ||
3179                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180                 if (gameMode == IcsObserving &&
3181                     atoi(star_match[0]) == ics_gamenum)
3182                   {
3183                       /* icsEngineAnalyze */
3184                       if (appData.icsEngineAnalyze) {
3185                             ExitAnalyzeMode();
3186                             ModeHighlight();
3187                       }
3188                       StopClocks();
3189                       gameMode = IcsIdle;
3190                       ics_gamenum = -1;
3191                       ics_user_moved = FALSE;
3192                   }
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "no longer examining game *")) {
3197                 if (gameMode == IcsExamining &&
3198                     atoi(star_match[0]) == ics_gamenum)
3199                   {
3200                       gameMode = IcsIdle;
3201                       ics_gamenum = -1;
3202                       ics_user_moved = FALSE;
3203                   }
3204                 continue;
3205             }
3206
3207             /* Advance leftover_start past any newlines we find,
3208                so only partial lines can get reparsed */
3209             if (looking_at(buf, &i, "\n")) {
3210                 prevColor = curColor;
3211                 if (curColor != ColorNormal) {
3212                     if (oldi > next_out) {
3213                         SendToPlayer(&buf[next_out], oldi - next_out);
3214                         next_out = oldi;
3215                     }
3216                     Colorize(ColorNormal, FALSE);
3217                     curColor = ColorNormal;
3218                 }
3219                 if (started == STARTED_BOARD) {
3220                     started = STARTED_NONE;
3221                     parse[parse_pos] = NULLCHAR;
3222                     ParseBoard12(parse);
3223                     ics_user_moved = 0;
3224
3225                     /* Send premove here */
3226                     if (appData.premove) {
3227                       char str[MSG_SIZ];
3228                       if (currentMove == 0 &&
3229                           gameMode == IcsPlayingWhite &&
3230                           appData.premoveWhite) {
3231                         sprintf(str, "%s%s\n", ics_prefix,
3232                                 appData.premoveWhiteText);
3233                         if (appData.debugMode)
3234                           fprintf(debugFP, "Sending premove:\n");
3235                         SendToICS(str);
3236                       } else if (currentMove == 1 &&
3237                                  gameMode == IcsPlayingBlack &&
3238                                  appData.premoveBlack) {
3239                         sprintf(str, "%s%s\n", ics_prefix,
3240                                 appData.premoveBlackText);
3241                         if (appData.debugMode)
3242                           fprintf(debugFP, "Sending premove:\n");
3243                         SendToICS(str);
3244                       } else if (gotPremove) {
3245                         gotPremove = 0;
3246                         ClearPremoveHighlights();
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                           UserMoveEvent(premoveFromX, premoveFromY,
3250                                         premoveToX, premoveToY,
3251                                         premovePromoChar);
3252                       }
3253                     }
3254
3255                     /* Usually suppress following prompt */
3256                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257                         if (looking_at(buf, &i, "*% ")) {
3258                             savingComment = FALSE;
3259                         }
3260                     }
3261                     next_out = i;
3262                 } else if (started == STARTED_HOLDINGS) {
3263                     int gamenum;
3264                     char new_piece[MSG_SIZ];
3265                     started = STARTED_NONE;
3266                     parse[parse_pos] = NULLCHAR;
3267                     if (appData.debugMode)
3268                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269                                                         parse, currentMove);
3270                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271                         gamenum == ics_gamenum) {
3272                         if (gameInfo.variant == VariantNormal) {
3273                           /* [HGM] We seem to switch variant during a game!
3274                            * Presumably no holdings were displayed, so we have
3275                            * to move the position two files to the right to
3276                            * create room for them!
3277                            */
3278                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279                           /* Get a move list just to see the header, which
3280                              will tell us whether this is really bug or zh */
3281                           if (ics_getting_history == H_FALSE) {
3282                             ics_getting_history = H_REQUESTED;
3283                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3284                             SendToICS(str);
3285                           }
3286                         }
3287                         new_piece[0] = NULLCHAR;
3288                         sscanf(parse, "game %d white [%s black [%s <- %s",
3289                                &gamenum, white_holding, black_holding,
3290                                new_piece);
3291                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3292                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3293                         /* [HGM] copy holdings to board holdings area */
3294                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3296 #if ZIPPY
3297                         if (appData.zippyPlay && first.initDone) {
3298                             ZippyHoldings(white_holding, black_holding,
3299                                           new_piece);
3300                         }
3301 #endif /*ZIPPY*/
3302                         if (tinyLayout || smallLayout) {
3303                             char wh[16], bh[16];
3304                             PackHolding(wh, white_holding);
3305                             PackHolding(bh, black_holding);
3306                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307                                     gameInfo.white, gameInfo.black);
3308                         } else {
3309                             sprintf(str, "%s [%s] vs. %s [%s]",
3310                                     gameInfo.white, white_holding,
3311                                     gameInfo.black, black_holding);
3312                         }
3313
3314                         DrawPosition(FALSE, boards[currentMove]);
3315                         DisplayTitle(str);
3316                     }
3317                     /* Suppress following prompt */
3318                     if (looking_at(buf, &i, "*% ")) {
3319                         savingComment = FALSE;
3320                     }
3321                     next_out = i;
3322                 }
3323                 continue;
3324             }
3325
3326             i++;                /* skip unparsed character and loop back */
3327         }
3328
3329         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330             started != STARTED_HOLDINGS && i > next_out) {
3331             SendToPlayer(&buf[next_out], i - next_out);
3332             next_out = i;
3333         }
3334         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3335
3336         leftover_len = buf_len - leftover_start;
3337         /* if buffer ends with something we couldn't parse,
3338            reparse it after appending the next read */
3339
3340     } else if (count == 0) {
3341         RemoveInputSource(isr);
3342         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3343     } else {
3344         DisplayFatalError(_("Error reading from ICS"), error, 1);
3345     }
3346 }
3347
3348
3349 /* Board style 12 looks like this:
3350
3351    <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
3352
3353  * The "<12> " is stripped before it gets to this routine.  The two
3354  * trailing 0's (flip state and clock ticking) are later addition, and
3355  * some chess servers may not have them, or may have only the first.
3356  * Additional trailing fields may be added in the future.
3357  */
3358
3359 #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"
3360
3361 #define RELATION_OBSERVING_PLAYED    0
3362 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE      1
3364 #define RELATION_PLAYING_NOTMYMOVE  -1
3365 #define RELATION_EXAMINING           2
3366 #define RELATION_ISOLATED_BOARD     -3
3367 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3368
3369 void
3370 ParseBoard12(string)
3371      char *string;
3372 {
3373     GameMode newGameMode;
3374     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377     char to_play, board_chars[200];
3378     char move_str[500], str[500], elapsed_time[500];
3379     char black[32], white[32];
3380     Board board;
3381     int prevMove = currentMove;
3382     int ticking = 2;
3383     ChessMove moveType;
3384     int fromX, fromY, toX, toY;
3385     char promoChar;
3386     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387     char *bookHit = NULL; // [HGM] book
3388
3389     fromX = fromY = toX = toY = -1;
3390
3391     newGame = FALSE;
3392
3393     if (appData.debugMode)
3394       fprintf(debugFP, _("Parsing board: %s\n"), string);
3395
3396     move_str[0] = NULLCHAR;
3397     elapsed_time[0] = NULLCHAR;
3398     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3399         int  i = 0, j;
3400         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401             if(string[i] == ' ') { ranks++; files = 0; }
3402             else files++;
3403             i++;
3404         }
3405         for(j = 0; j <i; j++) board_chars[j] = string[j];
3406         board_chars[i] = '\0';
3407         string += i + 1;
3408     }
3409     n = sscanf(string, PATTERN, &to_play, &double_push,
3410                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411                &gamenum, white, black, &relation, &basetime, &increment,
3412                &white_stren, &black_stren, &white_time, &black_time,
3413                &moveNum, str, elapsed_time, move_str, &ics_flip,
3414                &ticking);
3415
3416     if (n < 21) {
3417         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418         DisplayError(str, 0);
3419         return;
3420     }
3421
3422     /* Convert the move number to internal form */
3423     moveNum = (moveNum - 1) * 2;
3424     if (to_play == 'B') moveNum++;
3425     if (moveNum >= MAX_MOVES) {
3426       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3427                         0, 1);
3428       return;
3429     }
3430
3431     switch (relation) {
3432       case RELATION_OBSERVING_PLAYED:
3433       case RELATION_OBSERVING_STATIC:
3434         if (gamenum == -1) {
3435             /* Old ICC buglet */
3436             relation = RELATION_OBSERVING_STATIC;
3437         }
3438         newGameMode = IcsObserving;
3439         break;
3440       case RELATION_PLAYING_MYMOVE:
3441       case RELATION_PLAYING_NOTMYMOVE:
3442         newGameMode =
3443           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444             IcsPlayingWhite : IcsPlayingBlack;
3445         break;
3446       case RELATION_EXAMINING:
3447         newGameMode = IcsExamining;
3448         break;
3449       case RELATION_ISOLATED_BOARD:
3450       default:
3451         /* Just display this board.  If user was doing something else,
3452            we will forget about it until the next board comes. */
3453         newGameMode = IcsIdle;
3454         break;
3455       case RELATION_STARTING_POSITION:
3456         newGameMode = gameMode;
3457         break;
3458     }
3459
3460     /* Modify behavior for initial board display on move listing
3461        of wild games.
3462        */
3463     switch (ics_getting_history) {
3464       case H_FALSE:
3465       case H_REQUESTED:
3466         break;
3467       case H_GOT_REQ_HEADER:
3468       case H_GOT_UNREQ_HEADER:
3469         /* This is the initial position of the current game */
3470         gamenum = ics_gamenum;
3471         moveNum = 0;            /* old ICS bug workaround */
3472         if (to_play == 'B') {
3473           startedFromSetupPosition = TRUE;
3474           blackPlaysFirst = TRUE;
3475           moveNum = 1;
3476           if (forwardMostMove == 0) forwardMostMove = 1;
3477           if (backwardMostMove == 0) backwardMostMove = 1;
3478           if (currentMove == 0) currentMove = 1;
3479         }
3480         newGameMode = gameMode;
3481         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3482         break;
3483       case H_GOT_UNWANTED_HEADER:
3484         /* This is an initial board that we don't want */
3485         return;
3486       case H_GETTING_MOVES:
3487         /* Should not happen */
3488         DisplayError(_("Error gathering move list: extra board"), 0);
3489         ics_getting_history = H_FALSE;
3490         return;
3491     }
3492
3493     /* Take action if this is the first board of a new game, or of a
3494        different game than is currently being displayed.  */
3495     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496         relation == RELATION_ISOLATED_BOARD) {
3497
3498         /* Forget the old game and get the history (if any) of the new one */
3499         if (gameMode != BeginningOfGame) {
3500           Reset(FALSE, TRUE);
3501         }
3502         newGame = TRUE;
3503         if (appData.autoRaiseBoard) BoardToTop();
3504         prevMove = -3;
3505         if (gamenum == -1) {
3506             newGameMode = IcsIdle;
3507         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508                    appData.getMoveList) {
3509             /* Need to get game history */
3510             ics_getting_history = H_REQUESTED;
3511             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3512             SendToICS(str);
3513         }
3514
3515         /* Initially flip the board to have black on the bottom if playing
3516            black or if the ICS flip flag is set, but let the user change
3517            it with the Flip View button. */
3518         flipView = appData.autoFlipView ?
3519           (newGameMode == IcsPlayingBlack) || ics_flip :
3520           appData.flipView;
3521
3522         /* Done with values from previous mode; copy in new ones */
3523         gameMode = newGameMode;
3524         ModeHighlight();
3525         ics_gamenum = gamenum;
3526         if (gamenum == gs_gamenum) {
3527             int klen = strlen(gs_kind);
3528             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529             sprintf(str, "ICS %s", gs_kind);
3530             gameInfo.event = StrSave(str);
3531         } else {
3532             gameInfo.event = StrSave("ICS game");
3533         }
3534         gameInfo.site = StrSave(appData.icsHost);
3535         gameInfo.date = PGNDate();
3536         gameInfo.round = StrSave("-");
3537         gameInfo.white = StrSave(white);
3538         gameInfo.black = StrSave(black);
3539         timeControl = basetime * 60 * 1000;
3540         timeControl_2 = 0;
3541         timeIncrement = increment * 1000;
3542         movesPerSession = 0;
3543         gameInfo.timeControl = TimeControlTagValue();
3544         VariantSwitch(board, StringToVariant(gameInfo.event) );
3545   if (appData.debugMode) {
3546     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548     setbuf(debugFP, NULL);
3549   }
3550
3551         gameInfo.outOfBook = NULL;
3552
3553         /* Do we have the ratings? */
3554         if (strcmp(player1Name, white) == 0 &&
3555             strcmp(player2Name, black) == 0) {
3556             if (appData.debugMode)
3557               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558                       player1Rating, player2Rating);
3559             gameInfo.whiteRating = player1Rating;
3560             gameInfo.blackRating = player2Rating;
3561         } else if (strcmp(player2Name, white) == 0 &&
3562                    strcmp(player1Name, black) == 0) {
3563             if (appData.debugMode)
3564               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565                       player2Rating, player1Rating);
3566             gameInfo.whiteRating = player2Rating;
3567             gameInfo.blackRating = player1Rating;
3568         }
3569         player1Name[0] = player2Name[0] = NULLCHAR;
3570
3571         /* Silence shouts if requested */
3572         if (appData.quietPlay &&
3573             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574             SendToICS(ics_prefix);
3575             SendToICS("set shout 0\n");
3576         }
3577     }
3578
3579     /* Deal with midgame name changes */
3580     if (!newGame) {
3581         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582             if (gameInfo.white) free(gameInfo.white);
3583             gameInfo.white = StrSave(white);
3584         }
3585         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586             if (gameInfo.black) free(gameInfo.black);
3587             gameInfo.black = StrSave(black);
3588         }
3589     }
3590
3591     /* Throw away game result if anything actually changes in examine mode */
3592     if (gameMode == IcsExamining && !newGame) {
3593         gameInfo.result = GameUnfinished;
3594         if (gameInfo.resultDetails != NULL) {
3595             free(gameInfo.resultDetails);
3596             gameInfo.resultDetails = NULL;
3597         }
3598     }
3599
3600     /* In pausing && IcsExamining mode, we ignore boards coming
3601        in if they are in a different variation than we are. */
3602     if (pauseExamInvalid) return;
3603     if (pausing && gameMode == IcsExamining) {
3604         if (moveNum <= pauseExamForwardMostMove) {
3605             pauseExamInvalid = TRUE;
3606             forwardMostMove = pauseExamForwardMostMove;
3607             return;
3608         }
3609     }
3610
3611   if (appData.debugMode) {
3612     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3613   }
3614     /* Parse the board */
3615     for (k = 0; k < ranks; k++) {
3616       for (j = 0; j < files; j++)
3617         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618       if(gameInfo.holdingsWidth > 1) {
3619            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3621       }
3622     }
3623     CopyBoard(boards[moveNum], board);
3624     if (moveNum == 0) {
3625         startedFromSetupPosition =
3626           !CompareBoards(board, initialPosition);
3627         if(startedFromSetupPosition)
3628             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3629     }
3630
3631     /* [HGM] Set castling rights. Take the outermost Rooks,
3632        to make it also work for FRC opening positions. Note that board12
3633        is really defective for later FRC positions, as it has no way to
3634        indicate which Rook can castle if they are on the same side of King.
3635        For the initial position we grant rights to the outermost Rooks,
3636        and remember thos rights, and we then copy them on positions
3637        later in an FRC game. This means WB might not recognize castlings with
3638        Rooks that have moved back to their original position as illegal,
3639        but in ICS mode that is not its job anyway.
3640     */
3641     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3643
3644         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645             if(board[0][i] == WhiteRook) j = i;
3646         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648             if(board[0][i] == WhiteRook) j = i;
3649         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656
3657         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661             if(board[BOARD_HEIGHT-1][k] == bKing)
3662                 initialRights[5] = castlingRights[moveNum][5] = k;
3663     } else { int r;
3664         r = castlingRights[moveNum][0] = initialRights[0];
3665         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666         r = castlingRights[moveNum][1] = initialRights[1];
3667         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668         r = castlingRights[moveNum][3] = initialRights[3];
3669         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670         r = castlingRights[moveNum][4] = initialRights[4];
3671         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672         /* wildcastle kludge: always assume King has rights */
3673         r = castlingRights[moveNum][2] = initialRights[2];
3674         r = castlingRights[moveNum][5] = initialRights[5];
3675     }
3676     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3678
3679
3680     if (ics_getting_history == H_GOT_REQ_HEADER ||
3681         ics_getting_history == H_GOT_UNREQ_HEADER) {
3682         /* This was an initial position from a move list, not
3683            the current position */
3684         return;
3685     }
3686
3687     /* Update currentMove and known move number limits */
3688     newMove = newGame || moveNum > forwardMostMove;
3689
3690     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692         takeback = forwardMostMove - moveNum;
3693         for (i = 0; i < takeback; i++) {
3694              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695              SendToProgram("undo\n", &first);
3696         }
3697     }
3698
3699     if (newGame) {
3700         forwardMostMove = backwardMostMove = currentMove = moveNum;
3701         if (gameMode == IcsExamining && moveNum == 0) {
3702           /* Workaround for ICS limitation: we are not told the wild
3703              type when starting to examine a game.  But if we ask for
3704              the move list, the move list header will tell us */
3705             ics_getting_history = H_REQUESTED;
3706             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3707             SendToICS(str);
3708         }
3709     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711         forwardMostMove = moveNum;
3712         if (!pausing || currentMove > forwardMostMove)
3713           currentMove = forwardMostMove;
3714     } else {
3715         /* New part of history that is not contiguous with old part */
3716         if (pausing && gameMode == IcsExamining) {
3717             pauseExamInvalid = TRUE;
3718             forwardMostMove = pauseExamForwardMostMove;
3719             return;
3720         }
3721         forwardMostMove = backwardMostMove = currentMove = moveNum;
3722         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723             ics_getting_history = H_REQUESTED;
3724             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3725             SendToICS(str);
3726         }
3727     }
3728
3729     /* Update the clocks */
3730     if (strchr(elapsed_time, '.')) {
3731       /* Time is in ms */
3732       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3734     } else {
3735       /* Time is in seconds */
3736       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3738     }
3739
3740
3741 #if ZIPPY
3742     if (appData.zippyPlay && newGame &&
3743         gameMode != IcsObserving && gameMode != IcsIdle &&
3744         gameMode != IcsExamining)
3745       ZippyFirstBoard(moveNum, basetime, increment);
3746 #endif
3747
3748     /* Put the move on the move list, first converting
3749        to canonical algebraic form. */
3750     if (moveNum > 0) {
3751   if (appData.debugMode) {
3752     if (appData.debugMode) { int f = forwardMostMove;
3753         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3755     }
3756     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757     fprintf(debugFP, "moveNum = %d\n", moveNum);
3758     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759     setbuf(debugFP, NULL);
3760   }
3761         if (moveNum <= backwardMostMove) {
3762             /* We don't know what the board looked like before
3763                this move.  Punt. */
3764             strcpy(parseList[moveNum - 1], move_str);
3765             strcat(parseList[moveNum - 1], " ");
3766             strcat(parseList[moveNum - 1], elapsed_time);
3767             moveList[moveNum - 1][0] = NULLCHAR;
3768         } else if (strcmp(move_str, "none") == 0) {
3769             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770             /* Again, we don't know what the board looked like;
3771                this is really the start of the game. */
3772             parseList[moveNum - 1][0] = NULLCHAR;
3773             moveList[moveNum - 1][0] = NULLCHAR;
3774             backwardMostMove = moveNum;
3775             startedFromSetupPosition = TRUE;
3776             fromX = fromY = toX = toY = -1;
3777         } else {
3778           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3779           //                 So we parse the long-algebraic move string in stead of the SAN move
3780           int valid; char buf[MSG_SIZ], *prom;
3781
3782           // str looks something like "Q/a1-a2"; kill the slash
3783           if(str[1] == '/')
3784                 sprintf(buf, "%c%s", str[0], str+2);
3785           else  strcpy(buf, str); // might be castling
3786           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3787                 strcat(buf, prom); // long move lacks promo specification!
3788           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789                 if(appData.debugMode)
3790                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791                 strcpy(move_str, buf);
3792           }
3793           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794                                 &fromX, &fromY, &toX, &toY, &promoChar)
3795                || ParseOneMove(buf, moveNum - 1, &moveType,
3796                                 &fromX, &fromY, &toX, &toY, &promoChar);
3797           // end of long SAN patch
3798           if (valid) {
3799             (void) CoordsToAlgebraic(boards[moveNum - 1],
3800                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3801                                      fromY, fromX, toY, toX, promoChar,
3802                                      parseList[moveNum-1]);
3803             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804                              castlingRights[moveNum]) ) {
3805               case MT_NONE:
3806               case MT_STALEMATE:
3807               default:
3808                 break;
3809               case MT_CHECK:
3810                 if(gameInfo.variant != VariantShogi)
3811                     strcat(parseList[moveNum - 1], "+");
3812                 break;
3813               case MT_CHECKMATE:
3814               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815                 strcat(parseList[moveNum - 1], "#");
3816                 break;
3817             }
3818             strcat(parseList[moveNum - 1], " ");
3819             strcat(parseList[moveNum - 1], elapsed_time);
3820             /* currentMoveString is set as a side-effect of ParseOneMove */
3821             strcpy(moveList[moveNum - 1], currentMoveString);
3822             strcat(moveList[moveNum - 1], "\n");
3823           } else {
3824             /* Move from ICS was illegal!?  Punt. */
3825   if (appData.debugMode) {
3826     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3828   }
3829 #if 0
3830             if (appData.testLegality && appData.debugMode) {
3831                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832                 DisplayError(str, 0);
3833             }
3834 #endif
3835             strcpy(parseList[moveNum - 1], move_str);
3836             strcat(parseList[moveNum - 1], " ");
3837             strcat(parseList[moveNum - 1], elapsed_time);
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             fromX = fromY = toX = toY = -1;
3840           }
3841         }
3842   if (appData.debugMode) {
3843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844     setbuf(debugFP, NULL);
3845   }
3846
3847 #if ZIPPY
3848         /* Send move to chess program (BEFORE animating it). */
3849         if (appData.zippyPlay && !newGame && newMove &&
3850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851
3852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856                             move_str);
3857                     DisplayError(str, 0);
3858                 } else {
3859                     if (first.sendTime) {
3860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861                     }
3862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863                     if (firstMove && !bookHit) {
3864                         firstMove = FALSE;
3865                         if (first.useColors) {
3866                           SendToProgram(gameMode == IcsPlayingWhite ?
3867                                         "white\ngo\n" :
3868                                         "black\ngo\n", &first);
3869                         } else {
3870                           SendToProgram("go\n", &first);
3871                         }
3872                         first.maybeThinking = TRUE;
3873                     }
3874                 }
3875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876               if (moveList[moveNum - 1][0] == NULLCHAR) {
3877                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878                 DisplayError(str, 0);
3879               } else {
3880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881                 SendMoveToProgram(moveNum - 1, &first);
3882               }
3883             }
3884         }
3885 #endif
3886     }
3887
3888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889         /* If move comes from a remote source, animate it.  If it
3890            isn't remote, it will have already been animated. */
3891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893         }
3894         if (!pausing && appData.highlightLastMove) {
3895             SetHighlights(fromX, fromY, toX, toY);
3896         }
3897     }
3898
3899     /* Start the clocks */
3900     whiteFlag = blackFlag = FALSE;
3901     appData.clockMode = !(basetime == 0 && increment == 0);
3902     if (ticking == 0) {
3903       ics_clock_paused = TRUE;
3904       StopClocks();
3905     } else if (ticking == 1) {
3906       ics_clock_paused = FALSE;
3907     }
3908     if (gameMode == IcsIdle ||
3909         relation == RELATION_OBSERVING_STATIC ||
3910         relation == RELATION_EXAMINING ||
3911         ics_clock_paused)
3912       DisplayBothClocks();
3913     else
3914       StartClocks();
3915
3916     /* Display opponents and material strengths */
3917     if (gameInfo.variant != VariantBughouse &&
3918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919         if (tinyLayout || smallLayout) {
3920             if(gameInfo.variant == VariantNormal)
3921                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3923                     basetime, increment);
3924             else
3925                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3927                     basetime, increment, (int) gameInfo.variant);
3928         } else {
3929             if(gameInfo.variant == VariantNormal)
3930                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3931                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3932                     basetime, increment);
3933             else
3934                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3935                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3936                     basetime, increment, VariantName(gameInfo.variant));
3937         }
3938         DisplayTitle(str);
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3941   }
3942     }
3943
3944
3945     /* Display the board */
3946     if (!pausing && !appData.noGUI) {
3947       if (appData.premove)
3948           if (!gotPremove ||
3949              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951               ClearPremoveHighlights();
3952
3953       DrawPosition(FALSE, boards[currentMove]);
3954       DisplayMove(moveNum - 1);
3955       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3958         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3959       }
3960     }
3961
3962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3963 #if ZIPPY
3964     if(bookHit) { // [HGM] book: simulate book reply
3965         static char bookMove[MSG_SIZ]; // a bit generous?
3966
3967         programStats.nodes = programStats.depth = programStats.time =
3968         programStats.score = programStats.got_only_move = 0;
3969         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3970
3971         strcpy(bookMove, "move ");
3972         strcat(bookMove, bookHit);
3973         HandleMachineMove(bookMove, &first);
3974     }
3975 #endif
3976 }
3977
3978 void
3979 GetMoveListEvent()
3980 {
3981     char buf[MSG_SIZ];
3982     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983         ics_getting_history = H_REQUESTED;
3984         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3985         SendToICS(buf);
3986     }
3987 }
3988
3989 void
3990 AnalysisPeriodicEvent(force)
3991      int force;
3992 {
3993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994          && !force) || !appData.periodicUpdates)
3995       return;
3996
3997     /* Send . command to Crafty to collect stats */
3998     SendToProgram(".\n", &first);
3999
4000     /* Don't send another until we get a response (this makes
4001        us stop sending to old Crafty's which don't understand
4002        the "." command (sending illegal cmds resets node count & time,
4003        which looks bad)) */
4004     programStats.ok_to_send = 0;
4005 }
4006
4007 void
4008 SendMoveToProgram(moveNum, cps)
4009      int moveNum;
4010      ChessProgramState *cps;
4011 {
4012     char buf[MSG_SIZ];
4013
4014     if (cps->useUsermove) {
4015       SendToProgram("usermove ", cps);
4016     }
4017     if (cps->useSAN) {
4018       char *space;
4019       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4020         int len = space - parseList[moveNum];
4021         memcpy(buf, parseList[moveNum], len);
4022         buf[len++] = '\n';
4023         buf[len] = NULLCHAR;
4024       } else {
4025         sprintf(buf, "%s\n", parseList[moveNum]);
4026       }
4027       SendToProgram(buf, cps);
4028     } else {
4029       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4030         AlphaRank(moveList[moveNum], 4);
4031         SendToProgram(moveList[moveNum], cps);
4032         AlphaRank(moveList[moveNum], 4); // and back
4033       } else
4034       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4035        * the engine. It would be nice to have a better way to identify castle
4036        * moves here. */
4037       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4038                                                                          && cps->useOOCastle) {
4039         int fromX = moveList[moveNum][0] - AAA;
4040         int fromY = moveList[moveNum][1] - ONE;
4041         int toX = moveList[moveNum][2] - AAA;
4042         int toY = moveList[moveNum][3] - ONE;
4043         if((boards[moveNum][fromY][fromX] == WhiteKing
4044             && boards[moveNum][toY][toX] == WhiteRook)
4045            || (boards[moveNum][fromY][fromX] == BlackKing
4046                && boards[moveNum][toY][toX] == BlackRook)) {
4047           if(toX > fromX) SendToProgram("O-O\n", cps);
4048           else SendToProgram("O-O-O\n", cps);
4049         }
4050         else SendToProgram(moveList[moveNum], cps);
4051       }
4052       else SendToProgram(moveList[moveNum], cps);
4053       /* End of additions by Tord */
4054     }
4055
4056     /* [HGM] setting up the opening has brought engine in force mode! */
4057     /*       Send 'go' if we are in a mode where machine should play. */
4058     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4059         (gameMode == TwoMachinesPlay   ||
4060 #ifdef ZIPPY
4061          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4062 #endif
4063          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4064         SendToProgram("go\n", cps);
4065   if (appData.debugMode) {
4066     fprintf(debugFP, "(extra)\n");
4067   }
4068     }
4069     setboardSpoiledMachineBlack = 0;
4070 }
4071
4072 void
4073 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4074      ChessMove moveType;
4075      int fromX, fromY, toX, toY;
4076 {
4077     char user_move[MSG_SIZ];
4078
4079     switch (moveType) {
4080       default:
4081         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4082                 (int)moveType, fromX, fromY, toX, toY);
4083         DisplayError(user_move + strlen("say "), 0);
4084         break;
4085       case WhiteKingSideCastle:
4086       case BlackKingSideCastle:
4087       case WhiteQueenSideCastleWild:
4088       case BlackQueenSideCastleWild:
4089       /* PUSH Fabien */
4090       case WhiteHSideCastleFR:
4091       case BlackHSideCastleFR:
4092       /* POP Fabien */
4093         sprintf(user_move, "o-o\n");
4094         break;
4095       case WhiteQueenSideCastle:
4096       case BlackQueenSideCastle:
4097       case WhiteKingSideCastleWild:
4098       case BlackKingSideCastleWild:
4099       /* PUSH Fabien */
4100       case WhiteASideCastleFR:
4101       case BlackASideCastleFR:
4102       /* POP Fabien */
4103         sprintf(user_move, "o-o-o\n");
4104         break;
4105       case WhitePromotionQueen:
4106       case BlackPromotionQueen:
4107       case WhitePromotionRook:
4108       case BlackPromotionRook:
4109       case WhitePromotionBishop:
4110       case BlackPromotionBishop:
4111       case WhitePromotionKnight:
4112       case BlackPromotionKnight:
4113       case WhitePromotionKing:
4114       case BlackPromotionKing:
4115       case WhitePromotionChancellor:
4116       case BlackPromotionChancellor:
4117       case WhitePromotionArchbishop:
4118       case BlackPromotionArchbishop:
4119         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4120             sprintf(user_move, "%c%c%c%c=%c\n",
4121                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4122                 PieceToChar(WhiteFerz));
4123         else if(gameInfo.variant == VariantGreat)
4124             sprintf(user_move, "%c%c%c%c=%c\n",
4125                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4126                 PieceToChar(WhiteMan));
4127         else
4128             sprintf(user_move, "%c%c%c%c=%c\n",
4129                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4130                 PieceToChar(PromoPiece(moveType)));
4131         break;
4132       case WhiteDrop:
4133       case BlackDrop:
4134         sprintf(user_move, "%c@%c%c\n",
4135                 ToUpper(PieceToChar((ChessSquare) fromX)),
4136                 AAA + toX, ONE + toY);
4137         break;
4138       case NormalMove:
4139       case WhiteCapturesEnPassant:
4140       case BlackCapturesEnPassant:
4141       case IllegalMove:  /* could be a variant we don't quite understand */
4142         sprintf(user_move, "%c%c%c%c\n",
4143                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4144         break;
4145     }
4146     SendToICS(user_move);
4147     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4148         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4149 }
4150
4151 void
4152 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4153      int rf, ff, rt, ft;
4154      char promoChar;
4155      char move[7];
4156 {
4157     if (rf == DROP_RANK) {
4158         sprintf(move, "%c@%c%c\n",
4159                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4160     } else {
4161         if (promoChar == 'x' || promoChar == NULLCHAR) {
4162             sprintf(move, "%c%c%c%c\n",
4163                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4164         } else {
4165             sprintf(move, "%c%c%c%c%c\n",
4166                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4167         }
4168     }
4169 }
4170
4171 void
4172 ProcessICSInitScript(f)
4173      FILE *f;
4174 {
4175     char buf[MSG_SIZ];
4176
4177     while (fgets(buf, MSG_SIZ, f)) {
4178         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4179     }
4180
4181     fclose(f);
4182 }
4183
4184
4185 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4186 void
4187 AlphaRank(char *move, int n)
4188 {
4189 //    char *p = move, c; int x, y;
4190
4191     if (appData.debugMode) {
4192         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4193     }
4194
4195     if(move[1]=='*' &&
4196        move[2]>='0' && move[2]<='9' &&
4197        move[3]>='a' && move[3]<='x'    ) {
4198         move[1] = '@';
4199         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4200         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4201     } else
4202     if(move[0]>='0' && move[0]<='9' &&
4203        move[1]>='a' && move[1]<='x' &&
4204        move[2]>='0' && move[2]<='9' &&
4205        move[3]>='a' && move[3]<='x'    ) {
4206         /* input move, Shogi -> normal */
4207         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4208         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4209         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4210         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4211     } else
4212     if(move[1]=='@' &&
4213        move[3]>='0' && move[3]<='9' &&
4214        move[2]>='a' && move[2]<='x'    ) {
4215         move[1] = '*';
4216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4218     } else
4219     if(
4220        move[0]>='a' && move[0]<='x' &&
4221        move[3]>='0' && move[3]<='9' &&
4222        move[2]>='a' && move[2]<='x'    ) {
4223          /* output move, normal -> Shogi */
4224         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4225         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4226         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4227         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4228         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4229     }
4230     if (appData.debugMode) {
4231         fprintf(debugFP, "   out = '%s'\n", move);
4232     }
4233 }
4234
4235 /* Parser for moves from gnuchess, ICS, or user typein box */
4236 Boolean
4237 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4238      char *move;
4239      int moveNum;
4240      ChessMove *moveType;
4241      int *fromX, *fromY, *toX, *toY;
4242      char *promoChar;
4243 {
4244     if (appData.debugMode) {
4245         fprintf(debugFP, "move to parse: %s\n", move);
4246     }
4247     *moveType = yylexstr(moveNum, move);
4248
4249     switch (*moveType) {
4250       case WhitePromotionChancellor:
4251       case BlackPromotionChancellor:
4252       case WhitePromotionArchbishop:
4253       case BlackPromotionArchbishop:
4254       case WhitePromotionQueen:
4255       case BlackPromotionQueen:
4256       case WhitePromotionRook:
4257       case BlackPromotionRook:
4258       case WhitePromotionBishop:
4259       case BlackPromotionBishop:
4260       case WhitePromotionKnight:
4261       case BlackPromotionKnight:
4262       case WhitePromotionKing:
4263       case BlackPromotionKing:
4264       case NormalMove:
4265       case WhiteCapturesEnPassant:
4266       case BlackCapturesEnPassant:
4267       case WhiteKingSideCastle:
4268       case WhiteQueenSideCastle:
4269       case BlackKingSideCastle:
4270       case BlackQueenSideCastle:
4271       case WhiteKingSideCastleWild:
4272       case WhiteQueenSideCastleWild:
4273       case BlackKingSideCastleWild:
4274       case BlackQueenSideCastleWild:
4275       /* Code added by Tord: */
4276       case WhiteHSideCastleFR:
4277       case WhiteASideCastleFR:
4278       case BlackHSideCastleFR:
4279       case BlackASideCastleFR:
4280       /* End of code added by Tord */
4281       case IllegalMove:         /* bug or odd chess variant */
4282         *fromX = currentMoveString[0] - AAA;
4283         *fromY = currentMoveString[1] - ONE;
4284         *toX = currentMoveString[2] - AAA;
4285         *toY = currentMoveString[3] - ONE;
4286         *promoChar = currentMoveString[4];
4287         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4288             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4289     if (appData.debugMode) {
4290         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4291     }
4292             *fromX = *fromY = *toX = *toY = 0;
4293             return FALSE;
4294         }
4295         if (appData.testLegality) {
4296           return (*moveType != IllegalMove);
4297         } else {
4298           return !(fromX == fromY && toX == toY);
4299         }
4300
4301       case WhiteDrop:
4302       case BlackDrop:
4303         *fromX = *moveType == WhiteDrop ?
4304           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4305           (int) CharToPiece(ToLower(currentMoveString[0]));
4306         *fromY = DROP_RANK;
4307         *toX = currentMoveString[2] - AAA;
4308         *toY = currentMoveString[3] - ONE;
4309         *promoChar = NULLCHAR;
4310         return TRUE;
4311
4312       case AmbiguousMove:
4313       case ImpossibleMove:
4314       case (ChessMove) 0:       /* end of file */
4315       case ElapsedTime:
4316       case Comment:
4317       case PGNTag:
4318       case NAG:
4319       case WhiteWins:
4320       case BlackWins:
4321       case GameIsDrawn:
4322       default:
4323     if (appData.debugMode) {
4324         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4325     }
4326         /* bug? */
4327         *fromX = *fromY = *toX = *toY = 0;
4328         *promoChar = NULLCHAR;
4329         return FALSE;
4330     }
4331 }
4332
4333 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4334 // All positions will have equal probability, but the current method will not provide a unique
4335 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4336 #define DARK 1
4337 #define LITE 2
4338 #define ANY 3
4339
4340 int squaresLeft[4];
4341 int piecesLeft[(int)BlackPawn];
4342 int seed, nrOfShuffles;
4343
4344 void GetPositionNumber()
4345 {       // sets global variable seed
4346         int i;
4347
4348         seed = appData.defaultFrcPosition;
4349         if(seed < 0) { // randomize based on time for negative FRC position numbers
4350                 for(i=0; i<50; i++) seed += random();
4351                 seed = random() ^ random() >> 8 ^ random() << 8;
4352                 if(seed<0) seed = -seed;
4353         }
4354 }
4355
4356 int put(Board board, int pieceType, int rank, int n, int shade)
4357 // put the piece on the (n-1)-th empty squares of the given shade
4358 {
4359         int i;
4360
4361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4362                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4363                         board[rank][i] = (ChessSquare) pieceType;
4364                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4365                         squaresLeft[ANY]--;
4366                         piecesLeft[pieceType]--;
4367                         return i;
4368                 }
4369         }
4370         return -1;
4371 }
4372
4373
4374 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4375 // calculate where the next piece goes, (any empty square), and put it there
4376 {
4377         int i;
4378
4379         i = seed % squaresLeft[shade];
4380         nrOfShuffles *= squaresLeft[shade];
4381         seed /= squaresLeft[shade];
4382         put(board, pieceType, rank, i, shade);
4383 }
4384
4385 void AddTwoPieces(Board board, int pieceType, int rank)
4386 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4387 {
4388         int i, n=squaresLeft[ANY], j=n-1, k;
4389
4390         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4391         i = seed % k;  // pick one
4392         nrOfShuffles *= k;
4393         seed /= k;
4394         while(i >= j) i -= j--;
4395         j = n - 1 - j; i += j;
4396         put(board, pieceType, rank, j, ANY);
4397         put(board, pieceType, rank, i, ANY);
4398 }
4399
4400 void SetUpShuffle(Board board, int number)
4401 {
4402         int i, p, first=1;
4403
4404         GetPositionNumber(); nrOfShuffles = 1;
4405
4406         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4407         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4408         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4409
4410         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4411
4412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4413             p = (int) board[0][i];
4414             if(p < (int) BlackPawn) piecesLeft[p] ++;
4415             board[0][i] = EmptySquare;
4416         }
4417
4418         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4419             // shuffles restricted to allow normal castling put KRR first
4420             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4421                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4422             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4423                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4424             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4425                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4426             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4427                 put(board, WhiteRook, 0, 0, ANY);
4428             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4429         }
4430
4431         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4432             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4433             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4434                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4435                 while(piecesLeft[p] >= 2) {
4436                     AddOnePiece(board, p, 0, LITE);
4437                     AddOnePiece(board, p, 0, DARK);
4438                 }
4439                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4440             }
4441
4442         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4443             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4444             // but we leave King and Rooks for last, to possibly obey FRC restriction
4445             if(p == (int)WhiteRook) continue;
4446             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4447             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4448         }
4449
4450         // now everything is placed, except perhaps King (Unicorn) and Rooks
4451
4452         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4453             // Last King gets castling rights
4454             while(piecesLeft[(int)WhiteUnicorn]) {
4455                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4456                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4457             }
4458
4459             while(piecesLeft[(int)WhiteKing]) {
4460                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4461                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4462             }
4463
4464
4465         } else {
4466             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4467             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4468         }
4469
4470         // Only Rooks can be left; simply place them all
4471         while(piecesLeft[(int)WhiteRook]) {
4472                 i = put(board, WhiteRook, 0, 0, ANY);
4473                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4474                         if(first) {
4475                                 first=0;
4476                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4477                         }
4478                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4479                 }
4480         }
4481         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4482             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4483         }
4484
4485         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4486 }
4487
4488 int SetCharTable( char *table, const char * map )
4489 /* [HGM] moved here from winboard.c because of its general usefulness */
4490 /*       Basically a safe strcpy that uses the last character as King */
4491 {
4492     int result = FALSE; int NrPieces;
4493
4494     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4495                     && NrPieces >= 12 && !(NrPieces&1)) {
4496         int i; /* [HGM] Accept even length from 12 to 34 */
4497
4498         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4499         for( i=0; i<NrPieces/2-1; i++ ) {
4500             table[i] = map[i];
4501             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4502         }
4503         table[(int) WhiteKing]  = map[NrPieces/2-1];
4504         table[(int) BlackKing]  = map[NrPieces-1];
4505
4506         result = TRUE;
4507     }
4508
4509     return result;
4510 }
4511
4512 void Prelude(Board board)
4513 {       // [HGM] superchess: random selection of exo-pieces
4514         int i, j, k; ChessSquare p;
4515         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4516
4517         GetPositionNumber(); // use FRC position number
4518
4519         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4520             SetCharTable(pieceToChar, appData.pieceToCharTable);
4521             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4522                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4523         }
4524
4525         j = seed%4;                 seed /= 4;
4526         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4527         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4528         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4529         j = seed%3 + (seed%3 >= j); seed /= 3;
4530         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4531         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4532         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4533         j = seed%3;                 seed /= 3;
4534         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4535         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4536         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4537         j = seed%2 + (seed%2 >= j); seed /= 2;
4538         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4539         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4540         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4541         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4542         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4543         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4544         put(board, exoPieces[0],    0, 0, ANY);
4545         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4546 }
4547
4548 void
4549 InitPosition(redraw)
4550      int redraw;
4551 {
4552     ChessSquare (* pieces)[BOARD_SIZE];
4553     int i, j, pawnRow, overrule,
4554     oldx = gameInfo.boardWidth,
4555     oldy = gameInfo.boardHeight,
4556     oldh = gameInfo.holdingsWidth,
4557     oldv = gameInfo.variant;
4558
4559     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4560
4561     /* [AS] Initialize pv info list [HGM] and game status */
4562     {
4563         for( i=0; i<MAX_MOVES; i++ ) {
4564             pvInfoList[i].depth = 0;
4565             epStatus[i]=EP_NONE;
4566             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4567         }
4568
4569         initialRulePlies = 0; /* 50-move counter start */
4570
4571         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4572         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4573     }
4574
4575
4576     /* [HGM] logic here is completely changed. In stead of full positions */
4577     /* the initialized data only consist of the two backranks. The switch */
4578     /* selects which one we will use, which is than copied to the Board   */
4579     /* initialPosition, which for the rest is initialized by Pawns and    */
4580     /* empty squares. This initial position is then copied to boards[0],  */
4581     /* possibly after shuffling, so that it remains available.            */
4582
4583     gameInfo.holdingsWidth = 0; /* default board sizes */
4584     gameInfo.boardWidth    = 8;
4585     gameInfo.boardHeight   = 8;
4586     gameInfo.holdingsSize  = 0;
4587     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4588     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4589     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4590
4591     switch (gameInfo.variant) {
4592     case VariantFischeRandom:
4593       shuffleOpenings = TRUE;
4594     default:
4595       pieces = FIDEArray;
4596       break;
4597     case VariantShatranj:
4598       pieces = ShatranjArray;
4599       nrCastlingRights = 0;
4600       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4601       break;
4602     case VariantTwoKings:
4603       pieces = twoKingsArray;
4604       break;
4605     case VariantCapaRandom:
4606       shuffleOpenings = TRUE;
4607     case VariantCapablanca:
4608       pieces = CapablancaArray;
4609       gameInfo.boardWidth = 10;
4610       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4611       break;
4612     case VariantGothic:
4613       pieces = GothicArray;
4614       gameInfo.boardWidth = 10;
4615       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4616       break;
4617     case VariantJanus:
4618       pieces = JanusArray;
4619       gameInfo.boardWidth = 10;
4620       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4621       nrCastlingRights = 6;
4622         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4623         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4624         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4625         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4626         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4627         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4628       break;
4629     case VariantFalcon:
4630       pieces = FalconArray;
4631       gameInfo.boardWidth = 10;
4632       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4633       break;
4634     case VariantXiangqi:
4635       pieces = XiangqiArray;
4636       gameInfo.boardWidth  = 9;
4637       gameInfo.boardHeight = 10;
4638       nrCastlingRights = 0;
4639       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4640       break;
4641     case VariantShogi:
4642       pieces = ShogiArray;
4643       gameInfo.boardWidth  = 9;
4644       gameInfo.boardHeight = 9;
4645       gameInfo.holdingsSize = 7;
4646       nrCastlingRights = 0;
4647       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4648       break;
4649     case VariantCourier:
4650       pieces = CourierArray;
4651       gameInfo.boardWidth  = 12;
4652       nrCastlingRights = 0;
4653       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4654       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4655       break;
4656     case VariantKnightmate:
4657       pieces = KnightmateArray;
4658       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4659       break;
4660     case VariantFairy:
4661       pieces = fairyArray;
4662       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4663       break;
4664     case VariantGreat:
4665       pieces = GreatArray;
4666       gameInfo.boardWidth = 10;
4667       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4668       gameInfo.holdingsSize = 8;
4669       break;
4670     case VariantSuper:
4671       pieces = FIDEArray;
4672       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4673       gameInfo.holdingsSize = 8;
4674       startedFromSetupPosition = TRUE;
4675       break;
4676     case VariantCrazyhouse:
4677     case VariantBughouse:
4678       pieces = FIDEArray;
4679       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4680       gameInfo.holdingsSize = 5;
4681       break;
4682     case VariantWildCastle:
4683       pieces = FIDEArray;
4684       /* !!?shuffle with kings guaranteed to be on d or e file */
4685       shuffleOpenings = 1;
4686       break;
4687     case VariantNoCastle:
4688       pieces = FIDEArray;
4689       nrCastlingRights = 0;
4690       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4691       /* !!?unconstrained back-rank shuffle */
4692       shuffleOpenings = 1;
4693       break;
4694     }
4695
4696     overrule = 0;
4697     if(appData.NrFiles >= 0) {
4698         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4699         gameInfo.boardWidth = appData.NrFiles;
4700     }
4701     if(appData.NrRanks >= 0) {
4702         gameInfo.boardHeight = appData.NrRanks;
4703     }
4704     if(appData.holdingsSize >= 0) {
4705         i = appData.holdingsSize;
4706         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4707         gameInfo.holdingsSize = i;
4708     }
4709     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4710     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4711         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4712
4713     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4714     if(pawnRow < 1) pawnRow = 1;
4715
4716     /* User pieceToChar list overrules defaults */
4717     if(appData.pieceToCharTable != NULL)
4718         SetCharTable(pieceToChar, appData.pieceToCharTable);
4719
4720     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4721
4722         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4723             s = (ChessSquare) 0; /* account holding counts in guard band */
4724         for( i=0; i<BOARD_HEIGHT; i++ )
4725             initialPosition[i][j] = s;
4726
4727         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4728         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4729         initialPosition[pawnRow][j] = WhitePawn;
4730         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4731         if(gameInfo.variant == VariantXiangqi) {
4732             if(j&1) {
4733                 initialPosition[pawnRow][j] =
4734                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4735                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4736                    initialPosition[2][j] = WhiteCannon;
4737                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4738                 }
4739             }
4740         }
4741         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4742     }
4743     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4744
4745             j=BOARD_LEFT+1;
4746             initialPosition[1][j] = WhiteBishop;
4747             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4748             j=BOARD_RGHT-2;
4749             initialPosition[1][j] = WhiteRook;
4750             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4751     }
4752
4753     if( nrCastlingRights == -1) {
4754         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4755         /*       This sets default castling rights from none to normal corners   */
4756         /* Variants with other castling rights must set them themselves above    */
4757         nrCastlingRights = 6;
4758
4759         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4760         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4761         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4762         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4763         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4764         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4765      }
4766
4767      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4768      if(gameInfo.variant == VariantGreat) { // promotion commoners
4769         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4770         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4771         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4772         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4773      }
4774 #if 0
4775     if(gameInfo.variant == VariantFischeRandom) {
4776       if( appData.defaultFrcPosition < 0 ) {
4777         ShuffleFRC( initialPosition );
4778       }
4779       else {
4780         SetupFRC( initialPosition, appData.defaultFrcPosition );
4781       }
4782       startedFromSetupPosition = TRUE;
4783     } else
4784 #else
4785   if (appData.debugMode) {
4786     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4787   }
4788     if(shuffleOpenings) {
4789         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4790         startedFromSetupPosition = TRUE;
4791     }
4792 #endif
4793     if(startedFromPositionFile) {
4794       /* [HGM] loadPos: use PositionFile for every new game */
4795       CopyBoard(initialPosition, filePosition);
4796       for(i=0; i<nrCastlingRights; i++)
4797           castlingRights[0][i] = initialRights[i] = fileRights[i];
4798       startedFromSetupPosition = TRUE;
4799     }
4800
4801     CopyBoard(boards[0], initialPosition);
4802     if(oldx != gameInfo.boardWidth ||
4803        oldy != gameInfo.boardHeight ||
4804        oldh != gameInfo.holdingsWidth
4805 #ifdef GOTHIC
4806        || oldv == VariantGothic ||        // For licensing popups
4807        gameInfo.variant == VariantGothic
4808 #endif
4809 #ifdef FALCON
4810        || oldv == VariantFalcon ||
4811        gameInfo.variant == VariantFalcon
4812 #endif
4813                                          )
4814       {
4815             InitDrawingSizes(-2 ,0);
4816       }
4817
4818     if (redraw)
4819       DrawPosition(TRUE, boards[currentMove]);
4820
4821 }
4822
4823 void
4824 SendBoard(cps, moveNum)
4825      ChessProgramState *cps;
4826      int moveNum;
4827 {
4828     char message[MSG_SIZ];
4829
4830     if (cps->useSetboard) {
4831       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4832       sprintf(message, "setboard %s\n", fen);
4833       SendToProgram(message, cps);
4834       free(fen);
4835
4836     } else {
4837       ChessSquare *bp;
4838       int i, j;
4839       /* Kludge to set black to move, avoiding the troublesome and now
4840        * deprecated "black" command.
4841        */
4842       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4843
4844       SendToProgram("edit\n", cps);
4845       SendToProgram("#\n", cps);
4846       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4847         bp = &boards[moveNum][i][BOARD_LEFT];
4848         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4849           if ((int) *bp < (int) BlackPawn) {
4850             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4851                     AAA + j, ONE + i);
4852             if(message[0] == '+' || message[0] == '~') {
4853                 sprintf(message, "%c%c%c+\n",
4854                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4855                         AAA + j, ONE + i);
4856             }
4857             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4858                 message[1] = BOARD_RGHT   - 1 - j + '1';
4859                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860             }
4861             SendToProgram(message, cps);
4862           }
4863         }
4864       }
4865
4866       SendToProgram("c\n", cps);
4867       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868         bp = &boards[moveNum][i][BOARD_LEFT];
4869         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870           if (((int) *bp != (int) EmptySquare)
4871               && ((int) *bp >= (int) BlackPawn)) {
4872             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4873                     AAA + j, ONE + i);
4874             if(message[0] == '+' || message[0] == '~') {
4875                 sprintf(message, "%c%c%c+\n",
4876                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4877                         AAA + j, ONE + i);
4878             }
4879             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880                 message[1] = BOARD_RGHT   - 1 - j + '1';
4881                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882             }
4883             SendToProgram(message, cps);
4884           }
4885         }
4886       }
4887
4888       SendToProgram(".\n", cps);
4889     }
4890     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4891 }
4892
4893 int
4894 IsPromotion(fromX, fromY, toX, toY)
4895      int fromX, fromY, toX, toY;
4896 {
4897     /* [HGM] add Shogi promotions */
4898     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4899     ChessSquare piece;
4900
4901     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4902       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4903    /* [HGM] Note to self: line above also weeds out drops */
4904     piece = boards[currentMove][fromY][fromX];
4905     if(gameInfo.variant == VariantShogi) {
4906         promotionZoneSize = 3;
4907         highestPromotingPiece = (int)WhiteKing;
4908         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4909            and if in normal chess we then allow promotion to King, why not
4910            allow promotion of other piece in Shogi?                         */
4911     }
4912     if((int)piece >= BlackPawn) {
4913         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4914              return FALSE;
4915         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4916     } else {
4917         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4918            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919     }
4920     return ( (int)piece <= highestPromotingPiece );
4921 }
4922
4923 int
4924 InPalace(row, column)
4925      int row, column;
4926 {   /* [HGM] for Xiangqi */
4927     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4928          column < (BOARD_WIDTH + 4)/2 &&
4929          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4930     return FALSE;
4931 }
4932
4933 int
4934 PieceForSquare (x, y)
4935      int x;
4936      int y;
4937 {
4938   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4939      return -1;
4940   else
4941      return boards[currentMove][y][x];
4942 }
4943
4944 int
4945 OKToStartUserMove(x, y)
4946      int x, y;
4947 {
4948     ChessSquare from_piece;
4949     int white_piece;
4950
4951     if (matchMode) return FALSE;
4952     if (gameMode == EditPosition) return TRUE;
4953
4954     if (x >= 0 && y >= 0)
4955       from_piece = boards[currentMove][y][x];
4956     else
4957       from_piece = EmptySquare;
4958
4959     if (from_piece == EmptySquare) return FALSE;
4960
4961     white_piece = (int)from_piece >= (int)WhitePawn &&
4962       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4963
4964     switch (gameMode) {
4965       case PlayFromGameFile:
4966       case AnalyzeFile:
4967       case TwoMachinesPlay:
4968       case EndOfGame:
4969         return FALSE;
4970
4971       case IcsObserving:
4972       case IcsIdle:
4973         return FALSE;
4974
4975       case MachinePlaysWhite:
4976       case IcsPlayingBlack:
4977         if (appData.zippyPlay) return FALSE;
4978         if (white_piece) {
4979             DisplayMoveError(_("You are playing Black"));
4980             return FALSE;
4981         }
4982         break;
4983
4984       case MachinePlaysBlack:
4985       case IcsPlayingWhite:
4986         if (appData.zippyPlay) return FALSE;
4987         if (!white_piece) {
4988             DisplayMoveError(_("You are playing White"));
4989             return FALSE;
4990         }
4991         break;
4992
4993       case EditGame:
4994         if (!white_piece && WhiteOnMove(currentMove)) {
4995             DisplayMoveError(_("It is White's turn"));
4996             return FALSE;
4997         }
4998         if (white_piece && !WhiteOnMove(currentMove)) {
4999             DisplayMoveError(_("It is Black's turn"));
5000             return FALSE;
5001         }
5002         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5003             /* Editing correspondence game history */
5004             /* Could disallow this or prompt for confirmation */
5005             cmailOldMove = -1;
5006         }
5007         if (currentMove < forwardMostMove) {
5008             /* Discarding moves */
5009             /* Could prompt for confirmation here,
5010                but I don't think that's such a good idea */
5011             forwardMostMove = currentMove;
5012         }
5013         break;
5014
5015       case BeginningOfGame:
5016         if (appData.icsActive) return FALSE;
5017         if (!appData.noChessProgram) {
5018             if (!white_piece) {
5019                 DisplayMoveError(_("You are playing White"));
5020                 return FALSE;
5021             }
5022         }
5023         break;
5024
5025       case Training:
5026         if (!white_piece && WhiteOnMove(currentMove)) {
5027             DisplayMoveError(_("It is White's turn"));
5028             return FALSE;
5029         }
5030         if (white_piece && !WhiteOnMove(currentMove)) {
5031             DisplayMoveError(_("It is Black's turn"));
5032             return FALSE;
5033         }
5034         break;
5035
5036       default:
5037       case IcsExamining:
5038         break;
5039     }
5040     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5041         && gameMode != AnalyzeFile && gameMode != Training) {
5042         DisplayMoveError(_("Displayed position is not current"));
5043         return FALSE;
5044     }
5045     return TRUE;
5046 }
5047
5048 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5049 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5050 int lastLoadGameUseList = FALSE;
5051 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5052 ChessMove lastLoadGameStart = (ChessMove) 0;
5053
5054
5055 ChessMove
5056 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5057      int fromX, fromY, toX, toY;
5058      int promoChar;
5059 {
5060     ChessMove moveType;
5061     ChessSquare pdown, pup;
5062
5063     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064     if ((fromX == toX) && (fromY == toY)) {
5065         return ImpossibleMove;
5066     }
5067
5068     /* [HGM] suppress all moves into holdings area and guard band */
5069     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5070             return ImpossibleMove;
5071
5072     /* [HGM] <sameColor> moved to here from winboard.c */
5073     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5074     pdown = boards[currentMove][fromY][fromX];
5075     pup = boards[currentMove][toY][toX];
5076     if (    gameMode != EditPosition &&
5077             (WhitePawn <= pdown && pdown < BlackPawn &&
5078              WhitePawn <= pup && pup < BlackPawn  ||
5079              BlackPawn <= pdown && pdown < EmptySquare &&
5080              BlackPawn <= pup && pup < EmptySquare
5081             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5082                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5083                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  )
5084         )           )
5085          return ImpossibleMove;
5086
5087     /* Check if the user is playing in turn.  This is complicated because we
5088        let the user "pick up" a piece before it is his turn.  So the piece he
5089        tried to pick up may have been captured by the time he puts it down!
5090        Therefore we use the color the user is supposed to be playing in this
5091        test, not the color of the piece that is currently on the starting
5092        square---except in EditGame mode, where the user is playing both
5093        sides; fortunately there the capture race can't happen.  (It can
5094        now happen in IcsExamining mode, but that's just too bad.  The user
5095        will get a somewhat confusing message in that case.)
5096        */
5097
5098     switch (gameMode) {
5099       case PlayFromGameFile:
5100       case AnalyzeFile:
5101       case TwoMachinesPlay:
5102       case EndOfGame:
5103       case IcsObserving:
5104       case IcsIdle:
5105         /* We switched into a game mode where moves are not accepted,
5106            perhaps while the mouse button was down. */
5107         return ImpossibleMove;
5108
5109       case MachinePlaysWhite:
5110         /* User is moving for Black */
5111         if (WhiteOnMove(currentMove)) {
5112             DisplayMoveError(_("It is White's turn"));
5113             return ImpossibleMove;
5114         }
5115         break;
5116
5117       case MachinePlaysBlack:
5118         /* User is moving for White */
5119         if (!WhiteOnMove(currentMove)) {
5120             DisplayMoveError(_("It is Black's turn"));
5121             return ImpossibleMove;
5122         }
5123         break;
5124
5125       case EditGame:
5126       case IcsExamining:
5127       case BeginningOfGame:
5128       case AnalyzeMode:
5129       case Training:
5130         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5131             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5132             /* User is moving for Black */
5133             if (WhiteOnMove(currentMove)) {
5134                 DisplayMoveError(_("It is White's turn"));
5135                 return ImpossibleMove;
5136             }
5137         } else {
5138             /* User is moving for White */
5139             if (!WhiteOnMove(currentMove)) {
5140                 DisplayMoveError(_("It is Black's turn"));
5141                 return ImpossibleMove;
5142             }
5143         }
5144         break;
5145
5146       case IcsPlayingBlack:
5147         /* User is moving for Black */
5148         if (WhiteOnMove(currentMove)) {
5149             if (!appData.premove) {
5150                 DisplayMoveError(_("It is White's turn"));
5151             } else if (toX >= 0 && toY >= 0) {
5152                 premoveToX = toX;
5153                 premoveToY = toY;
5154                 premoveFromX = fromX;
5155                 premoveFromY = fromY;
5156                 premovePromoChar = promoChar;
5157                 gotPremove = 1;
5158                 if (appData.debugMode)
5159                     fprintf(debugFP, "Got premove: fromX %d,"
5160                             "fromY %d, toX %d, toY %d\n",
5161                             fromX, fromY, toX, toY);
5162                 if(!WhiteOnMove(currentMove) && gotPremove == 1) {
5163                     // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5164                     if (appData.debugMode) 
5165                         fprintf(debugFP, "Execute as normal move\n");
5166                     gotPremove = 0; break;
5167                 }
5168             }
5169             return ImpossibleMove;
5170         }
5171         break;
5172
5173       case IcsPlayingWhite:
5174         /* User is moving for White */
5175         if (!WhiteOnMove(currentMove)) {
5176             if (!appData.premove) {
5177                 DisplayMoveError(_("It is Black's turn"));
5178             } else if (toX >= 0 && toY >= 0) {
5179                 premoveToX = toX;
5180                 premoveToY = toY;
5181                 premoveFromX = fromX;
5182                 premoveFromY = fromY;
5183                 premovePromoChar = promoChar;
5184                 gotPremove = 1;
5185                 if (appData.debugMode)
5186                     fprintf(debugFP, "Got premove: fromX %d,"
5187                             "fromY %d, toX %d, toY %d\n",
5188                             fromX, fromY, toX, toY);
5189                 if(WhiteOnMove(currentMove) && gotPremove == 1) {
5190                     // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5191                     if (appData.debugMode) 
5192                         fprintf(debugFP, "Execute as normal move\n");
5193                     gotPremove = 0; break;
5194                 }
5195             }
5196             return ImpossibleMove;
5197         }
5198         break;
5199
5200       default:
5201         break;
5202
5203       case EditPosition:
5204         /* EditPosition, empty square, or different color piece;
5205            click-click move is possible */
5206         if (toX == -2 || toY == -2) {
5207             boards[0][fromY][fromX] = EmptySquare;
5208             return AmbiguousMove;
5209         } else if (toX >= 0 && toY >= 0) {
5210             boards[0][toY][toX] = boards[0][fromY][fromX];
5211             boards[0][fromY][fromX] = EmptySquare;
5212             return AmbiguousMove;
5213         }
5214         return ImpossibleMove;
5215     }
5216
5217     /* [HGM] If move started in holdings, it means a drop */
5218     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5219          if( pup != EmptySquare ) return ImpossibleMove;
5220          if(appData.testLegality) {
5221              /* it would be more logical if LegalityTest() also figured out
5222               * which drops are legal. For now we forbid pawns on back rank.
5223               * Shogi is on its own here...
5224               */
5225              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5226                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5227                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5228          }
5229          return WhiteDrop; /* Not needed to specify white or black yet */
5230     }
5231
5232     userOfferedDraw = FALSE;
5233
5234     /* [HGM] always test for legality, to get promotion info */
5235     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5236                           epStatus[currentMove], castlingRights[currentMove],
5237                                          fromY, fromX, toY, toX, promoChar);
5238
5239     /* [HGM] but possibly ignore an IllegalMove result */
5240     if (appData.testLegality) {
5241         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5242             DisplayMoveError(_("Illegal move"));
5243             return ImpossibleMove;
5244         }
5245     }
5246     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5247     return moveType;
5248     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5249        function is made into one that returns an OK move type if FinishMove
5250        should be called. This to give the calling driver routine the
5251        opportunity to finish the userMove input with a promotion popup,
5252        without bothering the user with this for invalid or illegal moves */
5253
5254 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5255 }
5256
5257 /* Common tail of UserMoveEvent and DropMenuEvent */
5258 int
5259 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5260      ChessMove moveType;
5261      int fromX, fromY, toX, toY;
5262      /*char*/int promoChar;
5263 {
5264   char *bookHit = 0;
5265
5266   if(appData.debugMode)
5267     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5268
5269   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5270     {
5271       // [HGM] superchess: suppress promotions to non-available piece
5272       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5273       if(WhiteOnMove(currentMove))
5274         {
5275           if(!boards[currentMove][k][BOARD_WIDTH-2])
5276             return 0;
5277         }
5278       else
5279         {
5280           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5281             return 0;
5282         }
5283     }
5284   
5285   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5286      move type in caller when we know the move is a legal promotion */
5287   if(moveType == NormalMove && promoChar)
5288     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5289
5290   if(appData.debugMode) 
5291     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5292
5293   /* [HGM] convert drag-and-drop piece drops to standard form */
5294   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5295     {
5296       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5297       if(appData.debugMode) 
5298         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5299                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5300       //         fromX = boards[currentMove][fromY][fromX];
5301       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5302       if(fromX == 0) 
5303         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5304
5305       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5306
5307       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5308         fromX++; 
5309
5310       fromY = DROP_RANK;
5311     }
5312
5313   /* [HGM] <popupFix> The following if has been moved here from
5314      UserMoveEvent(). Because it seemed to belon here (why not allow
5315      piece drops in training games?), and because it can only be
5316      performed after it is known to what we promote. */
5317   if (gameMode == Training)
5318     {
5319       /* compare the move played on the board to the next move in the
5320        * game. If they match, display the move and the opponent's response.
5321        * If they don't match, display an error message.
5322        */
5323       int saveAnimate;
5324       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5325       CopyBoard(testBoard, boards[currentMove]);
5326       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5327
5328       if (CompareBoards(testBoard, boards[currentMove+1]))
5329         {
5330           ForwardInner(currentMove+1);
5331
5332           /* Autoplay the opponent's response.
5333            * if appData.animate was TRUE when Training mode was entered,
5334            * the response will be animated.
5335            */
5336           saveAnimate = appData.animate;
5337           appData.animate = animateTraining;
5338           ForwardInner(currentMove+1);
5339           appData.animate = saveAnimate;
5340
5341           /* check for the end of the game */
5342           if (currentMove >= forwardMostMove)
5343             {
5344               gameMode = PlayFromGameFile;
5345               ModeHighlight();
5346               SetTrainingModeOff();
5347               DisplayInformation(_("End of game"));
5348             }
5349         }
5350       else
5351         {
5352           DisplayError(_("Incorrect move"), 0);
5353         }
5354       return 1;
5355     }
5356
5357   /* Ok, now we know that the move is good, so we can kill
5358      the previous line in Analysis Mode */
5359   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5360     {
5361       forwardMostMove = currentMove;
5362     }
5363
5364   /* If we need the chess program but it's dead, restart it */
5365   ResurrectChessProgram();
5366
5367   /* A user move restarts a paused game*/
5368   if (pausing)
5369     PauseEvent();
5370
5371   thinkOutput[0] = NULLCHAR;
5372
5373   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5374
5375   if (gameMode == BeginningOfGame)
5376     {
5377       if (appData.noChessProgram)
5378         {
5379           gameMode = EditGame;
5380           SetGameInfo();
5381         }
5382       else
5383         {
5384           char buf[MSG_SIZ];
5385           gameMode = MachinePlaysBlack;
5386           StartClocks();
5387           SetGameInfo();
5388           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5389           DisplayTitle(buf);
5390           if (first.sendName)
5391             {
5392               sprintf(buf, "name %s\n", gameInfo.white);
5393               SendToProgram(buf, &first);
5394             }
5395           StartClocks();
5396         }
5397       ModeHighlight();
5398     }
5399   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5400
5401   /* Relay move to ICS or chess engine */
5402   if (appData.icsActive)
5403     {
5404       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5405           gameMode == IcsExamining)
5406         {
5407           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5408           ics_user_moved = 1;
5409         }
5410     }
5411   else
5412     {
5413       if (first.sendTime && (gameMode == BeginningOfGame ||
5414                              gameMode == MachinePlaysWhite ||
5415                              gameMode == MachinePlaysBlack))
5416         {
5417           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5418         }
5419       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5420         {
5421           // [HGM] book: if program might be playing, let it use book
5422           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5423           first.maybeThinking = TRUE;
5424         }
5425       else
5426         SendMoveToProgram(forwardMostMove-1, &first);
5427       if (currentMove == cmailOldMove + 1)
5428         {
5429           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5430         }
5431     }
5432
5433   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5434
5435   switch (gameMode)
5436     {
5437     case EditGame:
5438       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5439                        EP_UNKNOWN, castlingRights[currentMove]) )
5440         {
5441         case MT_NONE:
5442         case MT_CHECK:
5443           break;
5444         case MT_CHECKMATE:
5445         case MT_STAINMATE:
5446           if (WhiteOnMove(currentMove))
5447             {
5448               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5449             }
5450           else
5451             {
5452               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5453             }
5454           break;
5455         case MT_STALEMATE:
5456           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5457           break;
5458     }
5459       break;
5460
5461     case MachinePlaysBlack:
5462     case MachinePlaysWhite:
5463       /* disable certain menu options while machine is thinking */
5464       SetMachineThinkingEnables();
5465       break;
5466
5467     default:
5468       break;
5469     }
5470
5471   if(bookHit)
5472     { // [HGM] book: simulate book reply
5473       static char bookMove[MSG_SIZ]; // a bit generous?
5474
5475       programStats.nodes = programStats.depth = programStats.time =
5476         programStats.score = programStats.got_only_move = 0;
5477       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5478
5479       strcpy(bookMove, "move ");
5480       strcat(bookMove, bookHit);
5481       HandleMachineMove(bookMove, &first);
5482     }
5483
5484   return 1;
5485 }
5486
5487 void
5488 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5489      int fromX, fromY, toX, toY;
5490      int promoChar;
5491 {
5492     /* [HGM] This routine was added to allow calling of its two logical
5493        parts from other modules in the old way. Before, UserMoveEvent()
5494        automatically called FinishMove() if the move was OK, and returned
5495        otherwise. I separated the two, in order to make it possible to
5496        slip a promotion popup in between. But that it always needs two
5497        calls, to the first part, (now called UserMoveTest() ), and to
5498        FinishMove if the first part succeeded. Calls that do not need
5499        to do anything in between, can call this routine the old way.
5500     */
5501     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5502 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5503     if(moveType == AmbiguousMove)
5504         DrawPosition(FALSE, boards[currentMove]);
5505     else if(moveType != ImpossibleMove)
5506         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5507 }
5508
5509 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5510 {
5511 //    char * hint = lastHint;
5512     FrontEndProgramStats stats;
5513
5514     stats.which = cps == &first ? 0 : 1;
5515     stats.depth = cpstats->depth;
5516     stats.nodes = cpstats->nodes;
5517     stats.score = cpstats->score;
5518     stats.time = cpstats->time;
5519     stats.pv = cpstats->movelist;
5520     stats.hint = lastHint;
5521     stats.an_move_index = 0;
5522     stats.an_move_count = 0;
5523
5524     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5525         stats.hint = cpstats->move_name;
5526         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5527         stats.an_move_count = cpstats->nr_moves;
5528     }
5529
5530     SetProgramStats( &stats );
5531 }
5532
5533 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5534 {   // [HGM] book: this routine intercepts moves to simulate book replies
5535     char *bookHit = NULL;
5536
5537     //first determine if the incoming move brings opponent into his book
5538     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5539         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5540     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5541     if(bookHit != NULL && !cps->bookSuspend) {
5542         // make sure opponent is not going to reply after receiving move to book position
5543         SendToProgram("force\n", cps);
5544         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5545     }
5546     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5547     // now arrange restart after book miss
5548     if(bookHit) {
5549         // after a book hit we never send 'go', and the code after the call to this routine
5550         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5551         char buf[MSG_SIZ];
5552         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5553         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5554         SendToProgram(buf, cps);
5555         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5556     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5557         SendToProgram("go\n", cps);
5558         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5559     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5560         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5561             SendToProgram("go\n", cps);
5562         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5563     }
5564     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5565 }
5566
5567 char *savedMessage;
5568 ChessProgramState *savedState;
5569 void DeferredBookMove(void)
5570 {
5571         if(savedState->lastPing != savedState->lastPong)
5572                     ScheduleDelayedEvent(DeferredBookMove, 10);
5573         else
5574         HandleMachineMove(savedMessage, savedState);
5575 }
5576
5577 void
5578 HandleMachineMove(message, cps)
5579      char *message;
5580      ChessProgramState *cps;
5581 {
5582     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5583     char realname[MSG_SIZ];
5584     int fromX, fromY, toX, toY;
5585     ChessMove moveType;
5586     char promoChar;
5587     char *p;
5588     int machineWhite;
5589     char *bookHit;
5590
5591 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5592     /*
5593      * Kludge to ignore BEL characters
5594      */
5595     while (*message == '\007') message++;
5596
5597     /*
5598      * [HGM] engine debug message: ignore lines starting with '#' character
5599      */
5600     if(cps->debug && *message == '#') return;
5601
5602     /*
5603      * Look for book output
5604      */
5605     if (cps == &first && bookRequested) {
5606         if (message[0] == '\t' || message[0] == ' ') {
5607             /* Part of the book output is here; append it */
5608             strcat(bookOutput, message);
5609             strcat(bookOutput, "  \n");
5610             return;
5611         } else if (bookOutput[0] != NULLCHAR) {
5612             /* All of book output has arrived; display it */
5613             char *p = bookOutput;
5614             while (*p != NULLCHAR) {
5615                 if (*p == '\t') *p = ' ';
5616                 p++;
5617             }
5618             DisplayInformation(bookOutput);
5619             bookRequested = FALSE;
5620             /* Fall through to parse the current output */
5621         }
5622     }
5623
5624     /*
5625      * Look for machine move.
5626      */
5627     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5628         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5629     {
5630         /* This method is only useful on engines that support ping */
5631         if (cps->lastPing != cps->lastPong) {
5632           if (gameMode == BeginningOfGame) {
5633             /* Extra move from before last new; ignore */
5634             if (appData.debugMode) {
5635                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5636             }
5637           } else {
5638             if (appData.debugMode) {
5639                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5640                         cps->which, gameMode);
5641             }
5642
5643             SendToProgram("undo\n", cps);
5644           }
5645           return;
5646         }
5647
5648         switch (gameMode) {
5649           case BeginningOfGame:
5650             /* Extra move from before last reset; ignore */
5651             if (appData.debugMode) {
5652                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5653             }
5654             return;
5655
5656           case EndOfGame:
5657           case IcsIdle:
5658           default:
5659             /* Extra move after we tried to stop.  The mode test is
5660                not a reliable way of detecting this problem, but it's
5661                the best we can do on engines that don't support ping.
5662             */
5663             if (appData.debugMode) {
5664                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5665                         cps->which, gameMode);
5666             }
5667             SendToProgram("undo\n", cps);
5668             return;
5669
5670           case MachinePlaysWhite:
5671           case IcsPlayingWhite:
5672             machineWhite = TRUE;
5673             break;
5674
5675           case MachinePlaysBlack:
5676           case IcsPlayingBlack:
5677             machineWhite = FALSE;
5678             break;
5679
5680           case TwoMachinesPlay:
5681             machineWhite = (cps->twoMachinesColor[0] == 'w');
5682             break;
5683         }
5684         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5685             if (appData.debugMode) {
5686                 fprintf(debugFP,
5687                         "Ignoring move out of turn by %s, gameMode %d"
5688                         ", forwardMost %d\n",
5689                         cps->which, gameMode, forwardMostMove);
5690             }
5691             return;
5692         }
5693
5694     if (appData.debugMode) { int f = forwardMostMove;
5695         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5696                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5697     }
5698         if(cps->alphaRank) AlphaRank(machineMove, 4);
5699         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5700                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5701             /* Machine move could not be parsed; ignore it. */
5702             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5703                     machineMove, cps->which);
5704             DisplayError(buf1, 0);
5705             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5706                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5707             if (gameMode == TwoMachinesPlay) {
5708               GameEnds(machineWhite ? BlackWins : WhiteWins,
5709                        buf1, GE_XBOARD);
5710             }
5711             return;
5712         }
5713
5714         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5715         /* So we have to redo legality test with true e.p. status here,  */
5716         /* to make sure an illegal e.p. capture does not slip through,   */
5717         /* to cause a forfeit on a justified illegal-move complaint      */
5718         /* of the opponent.                                              */
5719         if( gameMode==TwoMachinesPlay && appData.testLegality
5720             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5721                                                               ) {
5722            ChessMove moveType;
5723            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5724                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5725                              fromY, fromX, toY, toX, promoChar);
5726             if (appData.debugMode) {
5727                 int i;
5728                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5729                     castlingRights[forwardMostMove][i], castlingRank[i]);
5730                 fprintf(debugFP, "castling rights\n");
5731             }
5732             if(moveType == IllegalMove) {
5733                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5734                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5735                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5736                            buf1, GE_XBOARD);
5737                 return;
5738            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5739            /* [HGM] Kludge to handle engines that send FRC-style castling
5740               when they shouldn't (like TSCP-Gothic) */
5741            switch(moveType) {
5742              case WhiteASideCastleFR:
5743              case BlackASideCastleFR:
5744                toX+=2;
5745                currentMoveString[2]++;
5746                break;
5747              case WhiteHSideCastleFR:
5748              case BlackHSideCastleFR:
5749                toX--;
5750                currentMoveString[2]--;
5751                break;
5752              default: ; // nothing to do, but suppresses warning of pedantic compilers
5753            }
5754         }
5755         hintRequested = FALSE;
5756         lastHint[0] = NULLCHAR;
5757         bookRequested = FALSE;
5758         /* Program may be pondering now */
5759         cps->maybeThinking = TRUE;
5760         if (cps->sendTime == 2) cps->sendTime = 1;
5761         if (cps->offeredDraw) cps->offeredDraw--;
5762
5763 #if ZIPPY
5764         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5765             first.initDone) {
5766           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5767           ics_user_moved = 1;
5768           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5769                 char buf[3*MSG_SIZ];
5770
5771                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5772                         programStats.score / 100.,
5773                         programStats.depth,
5774                         programStats.time / 100.,
5775                         (unsigned int)programStats.nodes,
5776                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5777                         programStats.movelist);
5778                 SendToICS(buf);
5779 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5780           }
5781         }
5782 #endif
5783         /* currentMoveString is set as a side-effect of ParseOneMove */
5784         strcpy(machineMove, currentMoveString);
5785         strcat(machineMove, "\n");
5786         strcpy(moveList[forwardMostMove], machineMove);
5787
5788         /* [AS] Save move info and clear stats for next move */
5789         pvInfoList[ forwardMostMove ].score = programStats.score;
5790         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5791         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5792         ClearProgramStats();
5793         thinkOutput[0] = NULLCHAR;
5794         hiddenThinkOutputState = 0;
5795
5796         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5797
5798         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5799         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5800             int count = 0;
5801
5802             while( count < adjudicateLossPlies ) {
5803                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5804
5805                 if( count & 1 ) {
5806                     score = -score; /* Flip score for winning side */
5807                 }
5808
5809                 if( score > adjudicateLossThreshold ) {
5810                     break;
5811                 }
5812
5813                 count++;
5814             }
5815
5816             if( count >= adjudicateLossPlies ) {
5817                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5818
5819                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5820                     "Xboard adjudication",
5821                     GE_XBOARD );
5822
5823                 return;
5824             }
5825         }
5826
5827         if( gameMode == TwoMachinesPlay ) {
5828           // [HGM] some adjudications useful with buggy engines
5829             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5830           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5831
5832
5833             if( appData.testLegality )
5834             {   /* [HGM] Some more adjudications for obstinate engines */
5835                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5836                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5837                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5838                 static int moveCount = 6;
5839                 ChessMove result;
5840                 char *reason = NULL;
5841
5842                 /* Count what is on board. */
5843                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5844                 {   ChessSquare p = boards[forwardMostMove][i][j];
5845                     int m=i;
5846
5847                     switch((int) p)
5848                     {   /* count B,N,R and other of each side */
5849                         case WhiteKing:
5850                         case BlackKing:
5851                              NrK++; break; // [HGM] atomic: count Kings
5852                         case WhiteKnight:
5853                              NrWN++; break;
5854                         case WhiteBishop:
5855                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5856                              bishopsColor |= 1 << ((i^j)&1);
5857                              NrWB++; break;
5858                         case BlackKnight:
5859                              NrBN++; break;
5860                         case BlackBishop:
5861                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5862                              bishopsColor |= 1 << ((i^j)&1);
5863                              NrBB++; break;
5864                         case WhiteRook:
5865                              NrWR++; break;
5866                         case BlackRook:
5867                              NrBR++; break;
5868                         case WhiteQueen:
5869                              NrWQ++; break;
5870                         case BlackQueen:
5871                              NrBQ++; break;
5872                         case EmptySquare:
5873                              break;
5874                         case BlackPawn:
5875                              m = 7-i;
5876                         case WhitePawn:
5877                              PawnAdvance += m; NrPawns++;
5878                     }
5879                     NrPieces += (p != EmptySquare);
5880                     NrW += ((int)p < (int)BlackPawn);
5881                     if(gameInfo.variant == VariantXiangqi &&
5882                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5883                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5884                         NrW -= ((int)p < (int)BlackPawn);
5885                     }
5886                 }
5887
5888                 /* Some material-based adjudications that have to be made before stalemate test */
5889                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5890                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5891                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5892                      if(appData.checkMates) {
5893                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5894                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5895                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5896                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5897                          return;
5898                      }
5899                 }
5900
5901                 /* Bare King in Shatranj (loses) or Losers (wins) */
5902                 if( NrW == 1 || NrPieces - NrW == 1) {
5903                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5904                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5905                      if(appData.checkMates) {
5906                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5907                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5908                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5909                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5910                          return;
5911                      }
5912                   } else
5913                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5914                   {    /* bare King */
5915                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5916                         if(appData.checkMates) {
5917                             /* but only adjudicate if adjudication enabled */
5918                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5919                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5920                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5921                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5922                             return;
5923                         }
5924                   }
5925                 } else bare = 1;
5926
5927
5928             // don't wait for engine to announce game end if we can judge ourselves
5929             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5930                                        castlingRights[forwardMostMove]) ) {
5931               case MT_CHECK:
5932                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5933                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5934                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5935                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5936                             checkCnt++;
5937                         if(checkCnt >= 2) {
5938                             reason = "Xboard adjudication: 3rd check";
5939                             epStatus[forwardMostMove] = EP_CHECKMATE;
5940                             break;
5941                         }
5942                     }
5943                 }
5944               case MT_NONE:
5945               default:
5946                 break;
5947               case MT_STALEMATE:
5948               case MT_STAINMATE:
5949                 reason = "Xboard adjudication: Stalemate";
5950                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5951                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5952                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5953                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5954                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5955                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5956                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5957                                                                         EP_CHECKMATE : EP_WINS);
5958                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5959                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5960                 }
5961                 break;
5962               case MT_CHECKMATE:
5963                 reason = "Xboard adjudication: Checkmate";
5964                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5965                 break;
5966             }
5967
5968                 switch(i = epStatus[forwardMostMove]) {
5969                     case EP_STALEMATE:
5970                         result = GameIsDrawn; break;
5971                     case EP_CHECKMATE:
5972                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5973                     case EP_WINS:
5974                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5975                     default:
5976                         result = (ChessMove) 0;
5977                 }
5978                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5979                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5980                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5981                     GameEnds( result, reason, GE_XBOARD );
5982                     return;
5983                 }
5984
5985                 /* Next absolutely insufficient mating material. */
5986                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5987                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5988                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5989                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5990                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5991
5992                      /* always flag draws, for judging claims */
5993                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5994
5995                      if(appData.materialDraws) {
5996                          /* but only adjudicate them if adjudication enabled */
5997                          SendToProgram("force\n", cps->other); // suppress reply
5998                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5999                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6000                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6001                          return;
6002                      }
6003                 }
6004
6005                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6006                 if(NrPieces == 4 &&
6007                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6008                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6009                    || NrWN==2 || NrBN==2     /* KNNK */
6010                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6011                   ) ) {
6012                      if(--moveCount < 0 && appData.trivialDraws)
6013                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6014                           SendToProgram("force\n", cps->other); // suppress reply
6015                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6016                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6017                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6018                           return;
6019                      }
6020                 } else moveCount = 6;
6021             }
6022           }
6023 #if 1
6024     if (appData.debugMode) { int i;
6025       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6026               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6027               appData.drawRepeats);
6028       for( i=forwardMostMove; i>=backwardMostMove; i-- )
6029            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6030
6031     }
6032 #endif
6033                 /* Check for rep-draws */
6034                 count = 0;
6035                 for(k = forwardMostMove-2;
6036                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6037                         epStatus[k] < EP_UNKNOWN &&
6038                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6039                     k-=2)
6040                 {   int rights=0;
6041 #if 0
6042     if (appData.debugMode) {
6043       fprintf(debugFP, " loop\n");
6044     }
6045 #endif
6046                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6047 #if 0
6048     if (appData.debugMode) {
6049       fprintf(debugFP, "match\n");
6050     }
6051 #endif
6052                         /* compare castling rights */
6053                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6054                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6055                                 rights++; /* King lost rights, while rook still had them */
6056                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6057                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6058                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6059                                    rights++; /* but at least one rook lost them */
6060                         }
6061                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6062                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6063                                 rights++;
6064                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6065                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6066                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6067                                    rights++;
6068                         }
6069 #if 0
6070     if (appData.debugMode) {
6071       for(i=0; i<nrCastlingRights; i++)
6072       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6073     }
6074
6075     if (appData.debugMode) {
6076       fprintf(debugFP, " %d %d\n", rights, k);
6077     }
6078 #endif
6079                         if( rights == 0 && ++count > appData.drawRepeats-2
6080                             && appData.drawRepeats > 1) {
6081                              /* adjudicate after user-specified nr of repeats */
6082                              SendToProgram("force\n", cps->other); // suppress reply
6083                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6084                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6085                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6086                                 // [HGM] xiangqi: check for forbidden perpetuals
6087                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6088                                 for(m=forwardMostMove; m>k; m-=2) {
6089                                     if(MateTest(boards[m], PosFlags(m),
6090                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6091                                         ourPerpetual = 0; // the current mover did not always check
6092                                     if(MateTest(boards[m-1], PosFlags(m-1),
6093                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6094                                         hisPerpetual = 0; // the opponent did not always check
6095                                 }
6096                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6097                                                                         ourPerpetual, hisPerpetual);
6098                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6099                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6100                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6101                                     return;
6102                                 }
6103                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6104                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6105                                 // Now check for perpetual chases
6106                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6107                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6108                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6109                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6110                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6111                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6112                                         return;
6113                                     }
6114                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6115                                         break; // Abort repetition-checking loop.
6116                                 }
6117                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6118                              }
6119                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6120                              return;
6121                         }
6122                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6123                              epStatus[forwardMostMove] = EP_REP_DRAW;
6124                     }
6125                 }
6126
6127                 /* Now we test for 50-move draws. Determine ply count */
6128                 count = forwardMostMove;
6129                 /* look for last irreversble move */
6130                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6131                     count--;
6132                 /* if we hit starting position, add initial plies */
6133                 if( count == backwardMostMove )
6134                     count -= initialRulePlies;
6135                 count = forwardMostMove - count;
6136                 if( count >= 100)
6137                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6138                          /* this is used to judge if draw claims are legal */
6139                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6140                          SendToProgram("force\n", cps->other); // suppress reply
6141                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6142                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6143                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6144                          return;
6145                 }
6146
6147                 /* if draw offer is pending, treat it as a draw claim
6148                  * when draw condition present, to allow engines a way to
6149                  * claim draws before making their move to avoid a race
6150                  * condition occurring after their move
6151                  */
6152                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6153                          char *p = NULL;
6154                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6155                              p = "Draw claim: 50-move rule";
6156                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6157                              p = "Draw claim: 3-fold repetition";
6158                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6159                              p = "Draw claim: insufficient mating material";
6160                          if( p != NULL ) {
6161                              SendToProgram("force\n", cps->other); // suppress reply
6162                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6163                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6164                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6165                              return;
6166                          }
6167                 }
6168
6169
6170                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6171                     SendToProgram("force\n", cps->other); // suppress reply
6172                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6173                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6174
6175                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6176
6177                     return;
6178                 }
6179         }
6180
6181         bookHit = NULL;
6182         if (gameMode == TwoMachinesPlay) {
6183             /* [HGM] relaying draw offers moved to after reception of move */
6184             /* and interpreting offer as claim if it brings draw condition */
6185             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6186                 SendToProgram("draw\n", cps->other);
6187             }
6188             if (cps->other->sendTime) {
6189                 SendTimeRemaining(cps->other,
6190                                   cps->other->twoMachinesColor[0] == 'w');
6191             }
6192             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6193             if (firstMove && !bookHit) {
6194                 firstMove = FALSE;
6195                 if (cps->other->useColors) {
6196                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6197                 }
6198                 SendToProgram("go\n", cps->other);
6199             }
6200             cps->other->maybeThinking = TRUE;
6201         }
6202
6203         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6204
6205         if (!pausing && appData.ringBellAfterMoves) {
6206             RingBell();
6207         }
6208
6209         /*
6210          * Reenable menu items that were disabled while
6211          * machine was thinking
6212          */
6213         if (gameMode != TwoMachinesPlay)
6214             SetUserThinkingEnables();
6215
6216         // [HGM] book: after book hit opponent has received move and is now in force mode
6217         // force the book reply into it, and then fake that it outputted this move by jumping
6218         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6219         if(bookHit) {
6220                 static char bookMove[MSG_SIZ]; // a bit generous?
6221
6222                 strcpy(bookMove, "move ");
6223                 strcat(bookMove, bookHit);
6224                 message = bookMove;
6225                 cps = cps->other;
6226                 programStats.nodes = programStats.depth = programStats.time =
6227                 programStats.score = programStats.got_only_move = 0;
6228                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6229
6230                 if(cps->lastPing != cps->lastPong) {
6231                     savedMessage = message; // args for deferred call
6232                     savedState = cps;
6233                     ScheduleDelayedEvent(DeferredBookMove, 10);
6234                     return;
6235                 }
6236                 goto FakeBookMove;
6237         }
6238
6239         return;
6240     }
6241
6242     /* Set special modes for chess engines.  Later something general
6243      *  could be added here; for now there is just one kludge feature,
6244      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6245      *  when "xboard" is given as an interactive command.
6246      */
6247     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6248         cps->useSigint = FALSE;
6249         cps->useSigterm = FALSE;
6250     }
6251     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6252       ParseFeatures(message+8, cps);
6253       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6254     }
6255
6256     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6257      * want this, I was asked to put it in, and obliged.
6258      */
6259     if (!strncmp(message, "setboard ", 9)) {
6260         Board initial_position; int i;
6261
6262         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6263
6264         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6265             DisplayError(_("Bad FEN received from engine"), 0);
6266             return ;
6267         } else {
6268            Reset(FALSE, FALSE);
6269            CopyBoard(boards[0], initial_position);
6270            initialRulePlies = FENrulePlies;
6271            epStatus[0] = FENepStatus;
6272            for( i=0; i<nrCastlingRights; i++ )
6273                 castlingRights[0][i] = FENcastlingRights[i];
6274            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6275            else gameMode = MachinePlaysBlack;
6276            DrawPosition(FALSE, boards[currentMove]);
6277         }
6278         return;
6279     }
6280
6281     /*
6282      * Look for communication commands
6283      */
6284     if (!strncmp(message, "telluser ", 9)) {
6285         DisplayNote(message + 9);
6286         return;
6287     }
6288     if (!strncmp(message, "tellusererror ", 14)) {
6289         DisplayError(message + 14, 0);
6290         return;
6291     }
6292     if (!strncmp(message, "tellopponent ", 13)) {
6293       if (appData.icsActive) {
6294         if (loggedOn) {
6295           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6296           SendToICS(buf1);
6297         }
6298       } else {
6299         DisplayNote(message + 13);
6300       }
6301       return;
6302     }
6303     if (!strncmp(message, "tellothers ", 11)) {
6304       if (appData.icsActive) {
6305         if (loggedOn) {
6306           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6307           SendToICS(buf1);
6308         }
6309       }
6310       return;
6311     }
6312     if (!strncmp(message, "tellall ", 8)) {
6313       if (appData.icsActive) {
6314         if (loggedOn) {
6315           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6316           SendToICS(buf1);
6317         }
6318       } else {
6319         DisplayNote(message + 8);
6320       }
6321       return;
6322     }
6323     if (strncmp(message, "warning", 7) == 0) {
6324         /* Undocumented feature, use tellusererror in new code */
6325         DisplayError(message, 0);
6326         return;
6327     }
6328     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6329         strcpy(realname, cps->tidy);
6330         strcat(realname, " query");
6331         AskQuestion(realname, buf2, buf1, cps->pr);
6332         return;
6333     }
6334     /* Commands from the engine directly to ICS.  We don't allow these to be
6335      *  sent until we are logged on. Crafty kibitzes have been known to
6336      *  interfere with the login process.
6337      */
6338     if (loggedOn) {
6339         if (!strncmp(message, "tellics ", 8)) {
6340             SendToICS(message + 8);
6341             SendToICS("\n");
6342             return;
6343         }
6344         if (!strncmp(message, "tellicsnoalias ", 15)) {
6345             SendToICS(ics_prefix);
6346             SendToICS(message + 15);
6347             SendToICS("\n");
6348             return;
6349         }
6350         /* The following are for backward compatibility only */
6351         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6352             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6353             SendToICS(ics_prefix);
6354             SendToICS(message);
6355             SendToICS("\n");
6356             return;
6357         }
6358     }
6359     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6360         return;
6361     }
6362     /*
6363      * If the move is illegal, cancel it and redraw the board.
6364      * Also deal with other error cases.  Matching is rather loose
6365      * here to accommodate engines written before the spec.
6366      */
6367     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6368         strncmp(message, "Error", 5) == 0) {
6369         if (StrStr(message, "name") ||
6370             StrStr(message, "rating") || StrStr(message, "?") ||
6371             StrStr(message, "result") || StrStr(message, "board") ||
6372             StrStr(message, "bk") || StrStr(message, "computer") ||
6373             StrStr(message, "variant") || StrStr(message, "hint") ||
6374             StrStr(message, "random") || StrStr(message, "depth") ||
6375             StrStr(message, "accepted")) {
6376             return;
6377         }
6378         if (StrStr(message, "protover")) {
6379           /* Program is responding to input, so it's apparently done
6380              initializing, and this error message indicates it is
6381              protocol version 1.  So we don't need to wait any longer
6382              for it to initialize and send feature commands. */
6383           FeatureDone(cps, 1);
6384           cps->protocolVersion = 1;
6385           return;
6386         }
6387         cps->maybeThinking = FALSE;
6388
6389         if (StrStr(message, "draw")) {
6390             /* Program doesn't have "draw" command */
6391             cps->sendDrawOffers = 0;
6392             return;
6393         }
6394         if (cps->sendTime != 1 &&
6395             (StrStr(message, "time") || StrStr(message, "otim"))) {
6396           /* Program apparently doesn't have "time" or "otim" command */
6397           cps->sendTime = 0;
6398           return;
6399         }
6400         if (StrStr(message, "analyze")) {
6401             cps->analysisSupport = FALSE;
6402             cps->analyzing = FALSE;
6403             Reset(FALSE, TRUE);
6404             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6405             DisplayError(buf2, 0);
6406             return;
6407         }
6408         if (StrStr(message, "(no matching move)st")) {
6409           /* Special kludge for GNU Chess 4 only */
6410           cps->stKludge = TRUE;
6411           SendTimeControl(cps, movesPerSession, timeControl,
6412                           timeIncrement, appData.searchDepth,
6413                           searchTime);
6414           return;
6415         }
6416         if (StrStr(message, "(no matching move)sd")) {
6417           /* Special kludge for GNU Chess 4 only */
6418           cps->sdKludge = TRUE;
6419           SendTimeControl(cps, movesPerSession, timeControl,
6420                           timeIncrement, appData.searchDepth,
6421                           searchTime);
6422           return;
6423         }
6424         if (!StrStr(message, "llegal")) {
6425             return;
6426         }
6427         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6428             gameMode == IcsIdle) return;
6429         if (forwardMostMove <= backwardMostMove) return;
6430 #if 0
6431         /* Following removed: it caused a bug where a real illegal move
6432            message in analyze mored would be ignored. */
6433         if (cps == &first && programStats.ok_to_send == 0) {
6434             /* Bogus message from Crafty responding to "."  This filtering
6435                can miss some of the bad messages, but fortunately the bug
6436                is fixed in current Crafty versions, so it doesn't matter. */
6437             return;
6438         }
6439 #endif
6440         if (pausing) PauseEvent();
6441         if (gameMode == PlayFromGameFile) {
6442             /* Stop reading this game file */
6443             gameMode = EditGame;
6444             ModeHighlight();
6445         }
6446         currentMove = --forwardMostMove;
6447         DisplayMove(currentMove-1); /* before DisplayMoveError */
6448         SwitchClocks();
6449         DisplayBothClocks();
6450         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6451                 parseList[currentMove], cps->which);
6452         DisplayMoveError(buf1);
6453         DrawPosition(FALSE, boards[currentMove]);
6454
6455         /* [HGM] illegal-move claim should forfeit game when Xboard */
6456         /* only passes fully legal moves                            */
6457         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6458             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6459                                 "False illegal-move claim", GE_XBOARD );
6460         }
6461         return;
6462     }
6463     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6464         /* Program has a broken "time" command that
6465            outputs a string not ending in newline.
6466            Don't use it. */
6467         cps->sendTime = 0;
6468     }
6469
6470     /*
6471      * If chess program startup fails, exit with an error message.
6472      * Attempts to recover here are futile.
6473      */
6474     if ((StrStr(message, "unknown host") != NULL)
6475         || (StrStr(message, "No remote directory") != NULL)
6476         || (StrStr(message, "not found") != NULL)
6477         || (StrStr(message, "No such file") != NULL)
6478         || (StrStr(message, "can't alloc") != NULL)
6479         || (StrStr(message, "Permission denied") != NULL)) {
6480
6481         cps->maybeThinking = FALSE;
6482         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6483                 cps->which, cps->program, cps->host, message);
6484         RemoveInputSource(cps->isr);
6485         DisplayFatalError(buf1, 0, 1);
6486         return;
6487     }
6488
6489     /*
6490      * Look for hint output
6491      */
6492     if (sscanf(message, "Hint: %s", buf1) == 1) {
6493         if (cps == &first && hintRequested) {
6494             hintRequested = FALSE;
6495             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6496                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6497                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6498                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6499                                     fromY, fromX, toY, toX, promoChar, buf1);
6500                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6501                 DisplayInformation(buf2);
6502             } else {
6503                 /* Hint move could not be parsed!? */
6504               snprintf(buf2, sizeof(buf2),
6505                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6506                         buf1, cps->which);
6507                 DisplayError(buf2, 0);
6508             }
6509         } else {
6510             strcpy(lastHint, buf1);
6511         }
6512         return;
6513     }
6514
6515     /*
6516      * Ignore other messages if game is not in progress
6517      */
6518     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6519         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6520
6521     /*
6522      * look for win, lose, draw, or draw offer
6523      */
6524     if (strncmp(message, "1-0", 3) == 0) {
6525         char *p, *q, *r = "";
6526         p = strchr(message, '{');
6527         if (p) {
6528             q = strchr(p, '}');
6529             if (q) {
6530                 *q = NULLCHAR;
6531                 r = p + 1;
6532             }
6533         }
6534         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6535         return;
6536     } else if (strncmp(message, "0-1", 3) == 0) {
6537         char *p, *q, *r = "";
6538         p = strchr(message, '{');
6539         if (p) {
6540             q = strchr(p, '}');
6541             if (q) {
6542                 *q = NULLCHAR;
6543                 r = p + 1;
6544             }
6545         }
6546         /* Kludge for Arasan 4.1 bug */
6547         if (strcmp(r, "Black resigns") == 0) {
6548             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6549             return;
6550         }
6551         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6552         return;
6553     } else if (strncmp(message, "1/2", 3) == 0) {
6554         char *p, *q, *r = "";
6555         p = strchr(message, '{');
6556         if (p) {
6557             q = strchr(p, '}');
6558             if (q) {
6559                 *q = NULLCHAR;
6560                 r = p + 1;
6561             }
6562         }
6563
6564         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6565         return;
6566
6567     } else if (strncmp(message, "White resign", 12) == 0) {
6568         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6569         return;
6570     } else if (strncmp(message, "Black resign", 12) == 0) {
6571         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6572         return;
6573     } else if (strncmp(message, "White matches", 13) == 0 ||
6574                strncmp(message, "Black matches", 13) == 0   ) {
6575         /* [HGM] ignore GNUShogi noises */
6576         return;
6577     } else if (strncmp(message, "White", 5) == 0 &&
6578                message[5] != '(' &&
6579                StrStr(message, "Black") == NULL) {
6580         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6581         return;
6582     } else if (strncmp(message, "Black", 5) == 0 &&
6583                message[5] != '(') {
6584         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6585         return;
6586     } else if (strcmp(message, "resign") == 0 ||
6587                strcmp(message, "computer resigns") == 0) {
6588         switch (gameMode) {
6589           case MachinePlaysBlack:
6590           case IcsPlayingBlack:
6591             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6592             break;
6593           case MachinePlaysWhite:
6594           case IcsPlayingWhite:
6595             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6596             break;
6597           case TwoMachinesPlay:
6598             if (cps->twoMachinesColor[0] == 'w')
6599               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6600             else
6601               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6602             break;
6603           default:
6604             /* can't happen */
6605             break;
6606         }
6607         return;
6608     } else if (strncmp(message, "opponent mates", 14) == 0) {
6609         switch (gameMode) {
6610           case MachinePlaysBlack:
6611           case IcsPlayingBlack:
6612             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6613             break;
6614           case MachinePlaysWhite:
6615           case IcsPlayingWhite:
6616             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6617             break;
6618           case TwoMachinesPlay:
6619             if (cps->twoMachinesColor[0] == 'w')
6620               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6621             else
6622               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6623             break;
6624           default:
6625             /* can't happen */
6626             break;
6627         }
6628         return;
6629     } else if (strncmp(message, "computer mates", 14) == 0) {
6630         switch (gameMode) {
6631           case MachinePlaysBlack:
6632           case IcsPlayingBlack:
6633             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6634             break;
6635           case MachinePlaysWhite:
6636           case IcsPlayingWhite:
6637             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6638             break;
6639           case TwoMachinesPlay:
6640             if (cps->twoMachinesColor[0] == 'w')
6641               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6642             else
6643               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6644             break;
6645           default:
6646             /* can't happen */
6647             break;
6648         }
6649         return;
6650     } else if (strncmp(message, "checkmate", 9) == 0) {
6651         if (WhiteOnMove(forwardMostMove)) {
6652             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6653         } else {
6654             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6655         }
6656         return;
6657     } else if (strstr(message, "Draw") != NULL ||
6658                strstr(message, "game is a draw") != NULL) {
6659         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6660         return;
6661     } else if (strstr(message, "offer") != NULL &&
6662                strstr(message, "draw") != NULL) {
6663 #if ZIPPY
6664         if (appData.zippyPlay && first.initDone) {
6665             /* Relay offer to ICS */
6666             SendToICS(ics_prefix);
6667             SendToICS("draw\n");
6668         }
6669 #endif
6670         cps->offeredDraw = 2; /* valid until this engine moves twice */
6671         if (gameMode == TwoMachinesPlay) {
6672             if (cps->other->offeredDraw) {
6673                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6674             /* [HGM] in two-machine mode we delay relaying draw offer      */
6675             /* until after we also have move, to see if it is really claim */
6676             }
6677 #if 0
6678               else {
6679                 if (cps->other->sendDrawOffers) {
6680                     SendToProgram("draw\n", cps->other);
6681                 }
6682             }
6683 #endif
6684         } else if (gameMode == MachinePlaysWhite ||
6685                    gameMode == MachinePlaysBlack) {
6686           if (userOfferedDraw) {
6687             DisplayInformation(_("Machine accepts your draw offer"));
6688             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6689           } else {
6690             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6691           }
6692         }
6693     }
6694
6695
6696     /*
6697      * Look for thinking output
6698      */
6699     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6700           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6701                                 ) {
6702         int plylev, mvleft, mvtot, curscore, time;
6703         char mvname[MOVE_LEN];
6704         u64 nodes; // [DM]
6705         char plyext;
6706         int ignore = FALSE;
6707         int prefixHint = FALSE;
6708         mvname[0] = NULLCHAR;
6709
6710         switch (gameMode) {
6711           case MachinePlaysBlack:
6712           case IcsPlayingBlack:
6713             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6714             break;
6715           case MachinePlaysWhite:
6716           case IcsPlayingWhite:
6717             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6718             break;
6719           case AnalyzeMode:
6720           case AnalyzeFile:
6721             break;
6722           case IcsObserving: /* [DM] icsEngineAnalyze */
6723             if (!appData.icsEngineAnalyze) ignore = TRUE;
6724             break;
6725           case TwoMachinesPlay:
6726             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6727                 ignore = TRUE;
6728             }
6729             break;
6730           default:
6731             ignore = TRUE;
6732             break;
6733         }
6734
6735         if (!ignore) {
6736             buf1[0] = NULLCHAR;
6737             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6738                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6739
6740                 if (plyext != ' ' && plyext != '\t') {
6741                     time *= 100;
6742                 }
6743
6744                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6745                 if( cps->scoreIsAbsolute &&
6746                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6747                 {
6748                     curscore = -curscore;
6749                 }
6750
6751
6752                 programStats.depth = plylev;
6753                 programStats.nodes = nodes;
6754                 programStats.time = time;
6755                 programStats.score = curscore;
6756                 programStats.got_only_move = 0;
6757
6758                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6759                         int ticklen;
6760
6761                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6762                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6763                         if(WhiteOnMove(forwardMostMove))
6764                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6765                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6766                 }
6767
6768                 /* Buffer overflow protection */
6769                 if (buf1[0] != NULLCHAR) {
6770                     if (strlen(buf1) >= sizeof(programStats.movelist)
6771                         && appData.debugMode) {
6772                         fprintf(debugFP,
6773                                 "PV is too long; using the first %d bytes.\n",
6774                                 sizeof(programStats.movelist) - 1);
6775                     }
6776
6777                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6778                 } else {
6779                     sprintf(programStats.movelist, " no PV\n");
6780                 }
6781
6782                 if (programStats.seen_stat) {
6783                     programStats.ok_to_send = 1;
6784                 }
6785
6786                 if (strchr(programStats.movelist, '(') != NULL) {
6787                     programStats.line_is_book = 1;
6788                     programStats.nr_moves = 0;
6789                     programStats.moves_left = 0;
6790                 } else {
6791                     programStats.line_is_book = 0;
6792                 }
6793
6794                 SendProgramStatsToFrontend( cps, &programStats );
6795
6796                 /*
6797                     [AS] Protect the thinkOutput buffer from overflow... this
6798                     is only useful if buf1 hasn't overflowed first!
6799                 */
6800                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6801                         plylev,
6802                         (gameMode == TwoMachinesPlay ?
6803                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6804                         ((double) curscore) / 100.0,
6805                         prefixHint ? lastHint : "",
6806                         prefixHint ? " " : "" );
6807
6808                 if( buf1[0] != NULLCHAR ) {
6809                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6810
6811                     if( strlen(buf1) > max_len ) {
6812                         if( appData.debugMode) {
6813                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6814                         }
6815                         buf1[max_len+1] = '\0';
6816                     }
6817
6818                     strcat( thinkOutput, buf1 );
6819                 }
6820
6821                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6822                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6823                     DisplayMove(currentMove - 1);
6824                     DisplayAnalysis();
6825                 }
6826                 return;
6827
6828             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6829                 /* crafty (9.25+) says "(only move) <move>"
6830                  * if there is only 1 legal move
6831                  */
6832                 sscanf(p, "(only move) %s", buf1);
6833                 sprintf(thinkOutput, "%s (only move)", buf1);
6834                 sprintf(programStats.movelist, "%s (only move)", buf1);
6835                 programStats.depth = 1;
6836                 programStats.nr_moves = 1;
6837                 programStats.moves_left = 1;
6838                 programStats.nodes = 1;
6839                 programStats.time = 1;
6840                 programStats.got_only_move = 1;
6841
6842                 /* Not really, but we also use this member to
6843                    mean "line isn't going to change" (Crafty
6844                    isn't searching, so stats won't change) */
6845                 programStats.line_is_book = 1;
6846
6847                 SendProgramStatsToFrontend( cps, &programStats );
6848
6849                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6850                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6851                     DisplayMove(currentMove - 1);
6852                     DisplayAnalysis();
6853                 }
6854                 return;
6855             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6856                               &time, &nodes, &plylev, &mvleft,
6857                               &mvtot, mvname) >= 5) {
6858                 /* The stat01: line is from Crafty (9.29+) in response
6859                    to the "." command */
6860                 programStats.seen_stat = 1;
6861                 cps->maybeThinking = TRUE;
6862
6863                 if (programStats.got_only_move || !appData.periodicUpdates)
6864                   return;
6865
6866                 programStats.depth = plylev;
6867                 programStats.time = time;
6868                 programStats.nodes = nodes;
6869                 programStats.moves_left = mvleft;
6870                 programStats.nr_moves = mvtot;
6871                 strcpy(programStats.move_name, mvname);
6872                 programStats.ok_to_send = 1;
6873                 programStats.movelist[0] = '\0';
6874
6875                 SendProgramStatsToFrontend( cps, &programStats );
6876
6877                 DisplayAnalysis();
6878                 return;
6879
6880             } else if (strncmp(message,"++",2) == 0) {
6881                 /* Crafty 9.29+ outputs this */
6882                 programStats.got_fail = 2;
6883                 return;
6884
6885             } else if (strncmp(message,"--",2) == 0) {
6886                 /* Crafty 9.29+ outputs this */
6887                 programStats.got_fail = 1;
6888                 return;
6889
6890             } else if (thinkOutput[0] != NULLCHAR &&
6891                        strncmp(message, "    ", 4) == 0) {
6892                 unsigned message_len;
6893
6894                 p = message;
6895                 while (*p && *p == ' ') p++;
6896
6897                 message_len = strlen( p );
6898
6899                 /* [AS] Avoid buffer overflow */
6900                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6901                     strcat(thinkOutput, " ");
6902                     strcat(thinkOutput, p);
6903                 }
6904
6905                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6906                     strcat(programStats.movelist, " ");
6907                     strcat(programStats.movelist, p);
6908                 }
6909
6910                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6911                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6912                     DisplayMove(currentMove - 1);
6913                     DisplayAnalysis();
6914                 }
6915                 return;
6916             }
6917         }
6918         else {
6919             buf1[0] = NULLCHAR;
6920
6921             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6922                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6923             {
6924                 ChessProgramStats cpstats;
6925
6926                 if (plyext != ' ' && plyext != '\t') {
6927                     time *= 100;
6928                 }
6929
6930                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6931                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6932                     curscore = -curscore;
6933                 }
6934
6935                 cpstats.depth = plylev;
6936                 cpstats.nodes = nodes;
6937                 cpstats.time = time;
6938                 cpstats.score = curscore;
6939                 cpstats.got_only_move = 0;
6940                 cpstats.movelist[0] = '\0';
6941
6942                 if (buf1[0] != NULLCHAR) {
6943                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6944                 }
6945
6946                 cpstats.ok_to_send = 0;
6947                 cpstats.line_is_book = 0;
6948                 cpstats.nr_moves = 0;
6949                 cpstats.moves_left = 0;
6950
6951                 SendProgramStatsToFrontend( cps, &cpstats );
6952             }
6953         }
6954     }
6955 }
6956
6957
6958 /* Parse a game score from the character string "game", and
6959    record it as the history of the current game.  The game
6960    score is NOT assumed to start from the standard position.
6961    The display is not updated in any way.
6962    */
6963 void
6964 ParseGameHistory(game)
6965      char *game;
6966 {
6967     ChessMove moveType;
6968     int fromX, fromY, toX, toY, boardIndex;
6969     char promoChar;
6970     char *p, *q;
6971     char buf[MSG_SIZ];
6972
6973     if (appData.debugMode)
6974       fprintf(debugFP, "Parsing game history: %s\n", game);
6975
6976     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6977     gameInfo.site = StrSave(appData.icsHost);
6978     gameInfo.date = PGNDate();
6979     gameInfo.round = StrSave("-");
6980
6981     /* Parse out names of players */
6982     while (*game == ' ') game++;
6983     p = buf;
6984     while (*game != ' ') *p++ = *game++;
6985     *p = NULLCHAR;
6986     gameInfo.white = StrSave(buf);
6987     while (*game == ' ') game++;
6988     p = buf;
6989     while (*game != ' ' && *game != '\n') *p++ = *game++;
6990     *p = NULLCHAR;
6991     gameInfo.black = StrSave(buf);
6992
6993     /* Parse moves */
6994     boardIndex = blackPlaysFirst ? 1 : 0;
6995     yynewstr(game);
6996     for (;;) {
6997         yyboardindex = boardIndex;
6998         moveType = (ChessMove) yylex();
6999         switch (moveType) {
7000           case IllegalMove:             /* maybe suicide chess, etc. */
7001   if (appData.debugMode) {
7002     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7003     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7004     setbuf(debugFP, NULL);
7005   }
7006           case WhitePromotionChancellor:
7007           case BlackPromotionChancellor:
7008           case WhitePromotionArchbishop:
7009           case BlackPromotionArchbishop:
7010           case WhitePromotionQueen:
7011           case BlackPromotionQueen:
7012           case WhitePromotionRook:
7013           case BlackPromotionRook:
7014           case WhitePromotionBishop:
7015           case BlackPromotionBishop:
7016           case WhitePromotionKnight:
7017           case BlackPromotionKnight:
7018           case WhitePromotionKing:
7019           case BlackPromotionKing:
7020           case NormalMove:
7021           case WhiteCapturesEnPassant:
7022           case BlackCapturesEnPassant:
7023           case WhiteKingSideCastle:
7024           case WhiteQueenSideCastle:
7025           case BlackKingSideCastle:
7026           case BlackQueenSideCastle:
7027           case WhiteKingSideCastleWild:
7028           case WhiteQueenSideCastleWild:
7029           case BlackKingSideCastleWild:
7030           case BlackQueenSideCastleWild:
7031           /* PUSH Fabien */
7032           case WhiteHSideCastleFR:
7033           case WhiteASideCastleFR:
7034           case BlackHSideCastleFR:
7035           case BlackASideCastleFR:
7036           /* POP Fabien */
7037             fromX = currentMoveString[0] - AAA;
7038             fromY = currentMoveString[1] - ONE;
7039             toX = currentMoveString[2] - AAA;
7040             toY = currentMoveString[3] - ONE;
7041             promoChar = currentMoveString[4];
7042             break;
7043           case WhiteDrop:
7044           case BlackDrop:
7045             fromX = moveType == WhiteDrop ?
7046               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7047             (int) CharToPiece(ToLower(currentMoveString[0]));
7048             fromY = DROP_RANK;
7049             toX = currentMoveString[2] - AAA;
7050             toY = currentMoveString[3] - ONE;
7051             promoChar = NULLCHAR;
7052             break;
7053           case AmbiguousMove:
7054             /* bug? */
7055             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7056   if (appData.debugMode) {
7057     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7058     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7059     setbuf(debugFP, NULL);
7060   }
7061             DisplayError(buf, 0);
7062             return;
7063           case ImpossibleMove:
7064             /* bug? */
7065             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7066   if (appData.debugMode) {
7067     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7068     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7069     setbuf(debugFP, NULL);
7070   }
7071             DisplayError(buf, 0);
7072             return;
7073           case (ChessMove) 0:   /* end of file */
7074             if (boardIndex < backwardMostMove) {
7075                 /* Oops, gap.  How did that happen? */
7076                 DisplayError(_("Gap in move list"), 0);
7077                 return;
7078             }
7079             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7080             if (boardIndex > forwardMostMove) {
7081                 forwardMostMove = boardIndex;
7082             }
7083             return;
7084           case ElapsedTime:
7085             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7086                 strcat(parseList[boardIndex-1], " ");
7087                 strcat(parseList[boardIndex-1], yy_text);
7088             }
7089             continue;
7090           case Comment:
7091           case PGNTag:
7092           case NAG:
7093           default:
7094             /* ignore */
7095             continue;
7096           case WhiteWins:
7097           case BlackWins:
7098           case GameIsDrawn:
7099           case GameUnfinished:
7100             if (gameMode == IcsExamining) {
7101                 if (boardIndex < backwardMostMove) {
7102                     /* Oops, gap.  How did that happen? */
7103                     return;
7104                 }
7105                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7106                 return;
7107             }
7108             gameInfo.result = moveType;
7109             p = strchr(yy_text, '{');
7110             if (p == NULL) p = strchr(yy_text, '(');
7111             if (p == NULL) {
7112                 p = yy_text;
7113                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7114             } else {
7115                 q = strchr(p, *p == '{' ? '}' : ')');
7116                 if (q != NULL) *q = NULLCHAR;
7117                 p++;
7118             }
7119             gameInfo.resultDetails = StrSave(p);
7120             continue;
7121         }
7122         if (boardIndex >= forwardMostMove &&
7123             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7124             backwardMostMove = blackPlaysFirst ? 1 : 0;
7125             return;
7126         }
7127         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7128                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7129                                  parseList[boardIndex]);
7130         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7131         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7132         /* currentMoveString is set as a side-effect of yylex */
7133         strcpy(moveList[boardIndex], currentMoveString);
7134         strcat(moveList[boardIndex], "\n");
7135         boardIndex++;
7136         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7137                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7138         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7139                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7140           case MT_NONE:
7141           case MT_STALEMATE:
7142           default:
7143             break;
7144           case MT_CHECK:
7145             if(gameInfo.variant != VariantShogi)
7146                 strcat(parseList[boardIndex - 1], "+");
7147             break;
7148           case MT_CHECKMATE:
7149           case MT_STAINMATE:
7150             strcat(parseList[boardIndex - 1], "#");
7151             break;
7152         }
7153     }
7154 }
7155
7156
7157 /* Apply a move to the given board  */
7158 void
7159 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7160      int fromX, fromY, toX, toY;
7161      int promoChar;
7162      Board board;
7163      char *castling;
7164      char *ep;
7165 {
7166   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7167
7168     /* [HGM] compute & store e.p. status and castling rights for new position */
7169     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7170     { int i;
7171
7172       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7173       oldEP = *ep;
7174       *ep = EP_NONE;
7175
7176       if( board[toY][toX] != EmptySquare )
7177            *ep = EP_CAPTURE;
7178
7179       if( board[fromY][fromX] == WhitePawn ) {
7180            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7181                *ep = EP_PAWN_MOVE;
7182            if( toY-fromY==2) {
7183                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7184                         gameInfo.variant != VariantBerolina || toX < fromX)
7185                       *ep = toX | berolina;
7186                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7187                         gameInfo.variant != VariantBerolina || toX > fromX)
7188                       *ep = toX;
7189            }
7190       } else
7191       if( board[fromY][fromX] == BlackPawn ) {
7192            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7193                *ep = EP_PAWN_MOVE;
7194            if( toY-fromY== -2) {
7195                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7196                         gameInfo.variant != VariantBerolina || toX < fromX)
7197                       *ep = toX | berolina;
7198                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7199                         gameInfo.variant != VariantBerolina || toX > fromX)
7200                       *ep = toX;
7201            }
7202        }
7203
7204        for(i=0; i<nrCastlingRights; i++) {
7205            if(castling[i] == fromX && castlingRank[i] == fromY ||
7206               castling[i] == toX   && castlingRank[i] == toY
7207              ) castling[i] = -1; // revoke for moved or captured piece
7208        }
7209
7210     }
7211
7212   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7213   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7214        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7215
7216   if (fromX == toX && fromY == toY) return;
7217
7218   if (fromY == DROP_RANK) {
7219         /* must be first */
7220         piece = board[toY][toX] = (ChessSquare) fromX;
7221   } else {
7222      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7223      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7224      if(gameInfo.variant == VariantKnightmate)
7225          king += (int) WhiteUnicorn - (int) WhiteKing;
7226
7227     /* Code added by Tord: */
7228     /* FRC castling assumed when king captures friendly rook. */
7229     if (board[fromY][fromX] == WhiteKing &&
7230              board[toY][toX] == WhiteRook) {
7231       board[fromY][fromX] = EmptySquare;
7232       board[toY][toX] = EmptySquare;
7233       if(toX > fromX) {
7234         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7235       } else {
7236         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7237       }
7238     } else if (board[fromY][fromX] == BlackKing &&
7239                board[toY][toX] == BlackRook) {
7240       board[fromY][fromX] = EmptySquare;
7241       board[toY][toX] = EmptySquare;
7242       if(toX > fromX) {
7243         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7244       } else {
7245         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7246       }
7247     /* End of code added by Tord */
7248
7249     } else if (board[fromY][fromX] == king
7250         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7251         && toY == fromY && toX > fromX+1) {
7252         board[fromY][fromX] = EmptySquare;
7253         board[toY][toX] = king;
7254         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7255         board[fromY][BOARD_RGHT-1] = EmptySquare;
7256     } else if (board[fromY][fromX] == king
7257         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7258                && toY == fromY && toX < fromX-1) {
7259         board[fromY][fromX] = EmptySquare;
7260         board[toY][toX] = king;
7261         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7262         board[fromY][BOARD_LEFT] = EmptySquare;
7263     } else if (board[fromY][fromX] == WhitePawn
7264                && toY == BOARD_HEIGHT-1
7265                && gameInfo.variant != VariantXiangqi
7266                ) {
7267         /* white pawn promotion */
7268         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7269         if (board[toY][toX] == EmptySquare) {
7270             board[toY][toX] = WhiteQueen;
7271         }
7272         if(gameInfo.variant==VariantBughouse ||
7273            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7274             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7275         board[fromY][fromX] = EmptySquare;
7276     } else if ((fromY == BOARD_HEIGHT-4)
7277                && (toX != fromX)
7278                && gameInfo.variant != VariantXiangqi
7279                && gameInfo.variant != VariantBerolina
7280                && (board[fromY][fromX] == WhitePawn)
7281                && (board[toY][toX] == EmptySquare)) {
7282         board[fromY][fromX] = EmptySquare;
7283         board[toY][toX] = WhitePawn;
7284         captured = board[toY - 1][toX];
7285         board[toY - 1][toX] = EmptySquare;
7286     } else if ((fromY == BOARD_HEIGHT-4)
7287                && (toX == fromX)
7288                && gameInfo.variant == VariantBerolina
7289                && (board[fromY][fromX] == WhitePawn)
7290                && (board[toY][toX] == EmptySquare)) {
7291         board[fromY][fromX] = EmptySquare;
7292         board[toY][toX] = WhitePawn;
7293         if(oldEP & EP_BEROLIN_A) {
7294                 captured = board[fromY][fromX-1];
7295                 board[fromY][fromX-1] = EmptySquare;
7296         }else{  captured = board[fromY][fromX+1];
7297                 board[fromY][fromX+1] = EmptySquare;
7298         }
7299     } else if (board[fromY][fromX] == king
7300         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7301                && toY == fromY && toX > fromX+1) {
7302         board[fromY][fromX] = EmptySquare;
7303         board[toY][toX] = king;
7304         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7305         board[fromY][BOARD_RGHT-1] = EmptySquare;
7306     } else if (board[fromY][fromX] == king
7307         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7308                && toY == fromY && toX < fromX-1) {
7309         board[fromY][fromX] = EmptySquare;
7310         board[toY][toX] = king;
7311         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7312         board[fromY][BOARD_LEFT] = EmptySquare;
7313     } else if (fromY == 7 && fromX == 3
7314                && board[fromY][fromX] == BlackKing
7315                && toY == 7 && toX == 5) {
7316         board[fromY][fromX] = EmptySquare;
7317         board[toY][toX] = BlackKing;
7318         board[fromY][7] = EmptySquare;
7319         board[toY][4] = BlackRook;
7320     } else if (fromY == 7 && fromX == 3
7321                && board[fromY][fromX] == BlackKing
7322                && toY == 7 && toX == 1) {
7323         board[fromY][fromX] = EmptySquare;
7324         board[toY][toX] = BlackKing;
7325         board[fromY][0] = EmptySquare;
7326         board[toY][2] = BlackRook;
7327     } else if (board[fromY][fromX] == BlackPawn
7328                && toY == 0
7329                && gameInfo.variant != VariantXiangqi
7330                ) {
7331         /* black pawn promotion */
7332         board[0][toX] = CharToPiece(ToLower(promoChar));
7333         if (board[0][toX] == EmptySquare) {
7334             board[0][toX] = BlackQueen;
7335         }
7336         if(gameInfo.variant==VariantBughouse ||
7337            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7338             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7339         board[fromY][fromX] = EmptySquare;
7340     } else if ((fromY == 3)
7341                && (toX != fromX)
7342                && gameInfo.variant != VariantXiangqi
7343                && gameInfo.variant != VariantBerolina
7344                && (board[fromY][fromX] == BlackPawn)
7345                && (board[toY][toX] == EmptySquare)) {
7346         board[fromY][fromX] = EmptySquare;
7347         board[toY][toX] = BlackPawn;
7348         captured = board[toY + 1][toX];
7349         board[toY + 1][toX] = EmptySquare;
7350     } else if ((fromY == 3)
7351                && (toX == fromX)
7352                && gameInfo.variant == VariantBerolina
7353                && (board[fromY][fromX] == BlackPawn)
7354                && (board[toY][toX] == EmptySquare)) {
7355         board[fromY][fromX] = EmptySquare;
7356         board[toY][toX] = BlackPawn;
7357         if(oldEP & EP_BEROLIN_A) {
7358                 captured = board[fromY][fromX-1];
7359                 board[fromY][fromX-1] = EmptySquare;
7360         }else{  captured = board[fromY][fromX+1];
7361                 board[fromY][fromX+1] = EmptySquare;
7362         }
7363     } else {
7364         board[toY][toX] = board[fromY][fromX];
7365         board[fromY][fromX] = EmptySquare;
7366     }
7367
7368     /* [HGM] now we promote for Shogi, if needed */
7369     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7370         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7371   }
7372
7373     if (gameInfo.holdingsWidth != 0) {
7374
7375       /* !!A lot more code needs to be written to support holdings  */
7376       /* [HGM] OK, so I have written it. Holdings are stored in the */
7377       /* penultimate board files, so they are automaticlly stored   */
7378       /* in the game history.                                       */
7379       if (fromY == DROP_RANK) {
7380         /* Delete from holdings, by decreasing count */
7381         /* and erasing image if necessary            */
7382         p = (int) fromX;
7383         if(p < (int) BlackPawn) { /* white drop */
7384              p -= (int)WhitePawn;
7385              if(p >= gameInfo.holdingsSize) p = 0;
7386              if(--board[p][BOARD_WIDTH-2] == 0)
7387                   board[p][BOARD_WIDTH-1] = EmptySquare;
7388         } else {                  /* black drop */
7389              p -= (int)BlackPawn;
7390              if(p >= gameInfo.holdingsSize) p = 0;
7391              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7392                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7393         }
7394       }
7395       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7396           && gameInfo.variant != VariantBughouse        ) {
7397         /* [HGM] holdings: Add to holdings, if holdings exist */
7398         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7399                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7400                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7401         }
7402         p = (int) captured;
7403         if (p >= (int) BlackPawn) {
7404           p -= (int)BlackPawn;
7405           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7406                   /* in Shogi restore piece to its original  first */
7407                   captured = (ChessSquare) (DEMOTED captured);
7408                   p = DEMOTED p;
7409           }
7410           p = PieceToNumber((ChessSquare)p);
7411           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7412           board[p][BOARD_WIDTH-2]++;
7413           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7414         } else {
7415           p -= (int)WhitePawn;
7416           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7417                   captured = (ChessSquare) (DEMOTED captured);
7418                   p = DEMOTED p;
7419           }
7420           p = PieceToNumber((ChessSquare)p);
7421           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7422           board[BOARD_HEIGHT-1-p][1]++;
7423           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7424         }
7425       }
7426
7427     } else if (gameInfo.variant == VariantAtomic) {
7428       if (captured != EmptySquare) {
7429         int y, x;
7430         for (y = toY-1; y <= toY+1; y++) {
7431           for (x = toX-1; x <= toX+1; x++) {
7432             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7433                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7434               board[y][x] = EmptySquare;
7435             }
7436           }
7437         }
7438         board[toY][toX] = EmptySquare;
7439       }
7440     }
7441     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7442         /* [HGM] Shogi promotions */
7443         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7444     }
7445
7446     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7447                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7448         // [HGM] superchess: take promotion piece out of holdings
7449         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7450         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7451             if(!--board[k][BOARD_WIDTH-2])
7452                 board[k][BOARD_WIDTH-1] = EmptySquare;
7453         } else {
7454             if(!--board[BOARD_HEIGHT-1-k][1])
7455                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7456         }
7457     }
7458
7459 }
7460
7461 /* Updates forwardMostMove */
7462 void
7463 MakeMove(fromX, fromY, toX, toY, promoChar)
7464      int fromX, fromY, toX, toY;
7465      int promoChar;
7466 {
7467 //    forwardMostMove++; // [HGM] bare: moved downstream
7468
7469     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7470         int timeLeft; static int lastLoadFlag=0; int king, piece;
7471         piece = boards[forwardMostMove][fromY][fromX];
7472         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7473         if(gameInfo.variant == VariantKnightmate)
7474             king += (int) WhiteUnicorn - (int) WhiteKing;
7475         if(forwardMostMove == 0) {
7476             if(blackPlaysFirst)
7477                 fprintf(serverMoves, "%s;", second.tidy);
7478             fprintf(serverMoves, "%s;", first.tidy);
7479             if(!blackPlaysFirst)
7480                 fprintf(serverMoves, "%s;", second.tidy);
7481         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7482         lastLoadFlag = loadFlag;
7483         // print base move
7484         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7485         // print castling suffix
7486         if( toY == fromY && piece == king ) {
7487             if(toX-fromX > 1)
7488                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7489             if(fromX-toX >1)
7490                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7491         }
7492         // e.p. suffix
7493         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7494              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7495              boards[forwardMostMove][toY][toX] == EmptySquare
7496              && fromX != toX )
7497                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7498         // promotion suffix
7499         if(promoChar != NULLCHAR)
7500                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7501         if(!loadFlag) {
7502             fprintf(serverMoves, "/%d/%d",
7503                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7504             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7505             else                      timeLeft = blackTimeRemaining/1000;
7506             fprintf(serverMoves, "/%d", timeLeft);
7507         }
7508         fflush(serverMoves);
7509     }
7510
7511     if (forwardMostMove+1 >= MAX_MOVES) {
7512       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7513                         0, 1);
7514       return;
7515     }
7516     SwitchClocks();
7517     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7518     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7519     if (commentList[forwardMostMove+1] != NULL) {
7520         free(commentList[forwardMostMove+1]);
7521         commentList[forwardMostMove+1] = NULL;
7522     }
7523     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7524     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7525     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7526                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7527     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7528     gameInfo.result = GameUnfinished;
7529     if (gameInfo.resultDetails != NULL) {
7530         free(gameInfo.resultDetails);
7531         gameInfo.resultDetails = NULL;
7532     }
7533     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7534                               moveList[forwardMostMove - 1]);
7535     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7536                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7537                              fromY, fromX, toY, toX, promoChar,
7538                              parseList[forwardMostMove - 1]);
7539     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7540                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7541                             castlingRights[forwardMostMove]) ) {
7542       case MT_NONE:
7543       case MT_STALEMATE:
7544       default:
7545         break;
7546       case MT_CHECK:
7547         if(gameInfo.variant != VariantShogi)
7548             strcat(parseList[forwardMostMove - 1], "+");
7549         break;
7550       case MT_CHECKMATE:
7551       case MT_STAINMATE:
7552         strcat(parseList[forwardMostMove - 1], "#");
7553         break;
7554     }
7555     if (appData.debugMode) {
7556         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7557     }
7558
7559 }
7560
7561 /* Updates currentMove if not pausing */
7562 void
7563 ShowMove(fromX, fromY, toX, toY)
7564 {
7565     int instant = (gameMode == PlayFromGameFile) ?
7566         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7567
7568     if(appData.noGUI) return;
7569
7570     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7571       {
7572         if (!instant)
7573           {
7574             if (forwardMostMove == currentMove + 1)
7575               {
7576 //TODO
7577 //              AnimateMove(boards[forwardMostMove - 1],
7578 //                          fromX, fromY, toX, toY);
7579               }
7580             if (appData.highlightLastMove)
7581               {
7582                 SetHighlights(fromX, fromY, toX, toY);
7583               }
7584           }
7585         currentMove = forwardMostMove;
7586     }
7587
7588     if (instant) return;
7589
7590     DisplayMove(currentMove - 1);
7591     DrawPosition(FALSE, boards[currentMove]);
7592     DisplayBothClocks();
7593     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7594
7595     return;
7596 }
7597
7598 void SendEgtPath(ChessProgramState *cps)
7599 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7600         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7601
7602         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7603
7604         while(*p) {
7605             char c, *q = name+1, *r, *s;
7606
7607             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7608             while(*p && *p != ',') *q++ = *p++;
7609             *q++ = ':'; *q = 0;
7610             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7611                 strcmp(name, ",nalimov:") == 0 ) {
7612                 // take nalimov path from the menu-changeable option first, if it is defined
7613                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7614                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7615             } else
7616             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7617                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7618                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7619                 s = r = StrStr(s, ":") + 1; // beginning of path info
7620                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7621                 c = *r; *r = 0;             // temporarily null-terminate path info
7622                     *--q = 0;               // strip of trailig ':' from name
7623                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7624                 *r = c;
7625                 SendToProgram(buf,cps);     // send egtbpath command for this format
7626             }
7627             if(*p == ',') p++; // read away comma to position for next format name
7628         }
7629 }
7630
7631 void
7632 InitChessProgram(cps, setup)
7633      ChessProgramState *cps;
7634      int setup; /* [HGM] needed to setup FRC opening position */
7635 {
7636     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7637     if (appData.noChessProgram) return;
7638     hintRequested = FALSE;
7639     bookRequested = FALSE;
7640
7641     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7642     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7643     if(cps->memSize) { /* [HGM] memory */
7644         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7645         SendToProgram(buf, cps);
7646     }
7647     SendEgtPath(cps); /* [HGM] EGT */
7648     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7649         sprintf(buf, "cores %d\n", appData.smpCores);
7650         SendToProgram(buf, cps);
7651     }
7652
7653     SendToProgram(cps->initString, cps);
7654     if (gameInfo.variant != VariantNormal &&
7655         gameInfo.variant != VariantLoadable
7656         /* [HGM] also send variant if board size non-standard */
7657         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7658                                             ) {
7659       char *v = VariantName(gameInfo.variant);
7660       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7661         /* [HGM] in protocol 1 we have to assume all variants valid */
7662         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7663         DisplayFatalError(buf, 0, 1);
7664         return;
7665       }
7666
7667       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7668       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7669       if( gameInfo.variant == VariantXiangqi )
7670            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7671       if( gameInfo.variant == VariantShogi )
7672            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7673       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7674            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7675       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7676                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7677            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7678       if( gameInfo.variant == VariantCourier )
7679            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7680       if( gameInfo.variant == VariantSuper )
7681            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7682       if( gameInfo.variant == VariantGreat )
7683            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7684
7685       if(overruled) {
7686            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7687                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7688            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7689            if(StrStr(cps->variants, b) == NULL) {
7690                // specific sized variant not known, check if general sizing allowed
7691                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7692                    if(StrStr(cps->variants, "boardsize") == NULL) {
7693                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7694                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7695                        DisplayFatalError(buf, 0, 1);
7696                        return;
7697                    }
7698                    /* [HGM] here we really should compare with the maximum supported board size */
7699                }
7700            }
7701       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7702       sprintf(buf, "variant %s\n", b);
7703       SendToProgram(buf, cps);
7704     }
7705     currentlyInitializedVariant = gameInfo.variant;
7706
7707     /* [HGM] send opening position in FRC to first engine */
7708     if(setup) {
7709           SendToProgram("force\n", cps);
7710           SendBoard(cps, 0);
7711           /* engine is now in force mode! Set flag to wake it up after first move. */
7712           setboardSpoiledMachineBlack = 1;
7713     }
7714
7715     if (cps->sendICS) {
7716       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7717       SendToProgram(buf, cps);
7718     }
7719     cps->maybeThinking = FALSE;
7720     cps->offeredDraw = 0;
7721     if (!appData.icsActive) {
7722         SendTimeControl(cps, movesPerSession, timeControl,
7723                         timeIncrement, appData.searchDepth,
7724                         searchTime);
7725     }
7726     if (appData.showThinking
7727         // [HGM] thinking: four options require thinking output to be sent
7728         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7729                                 ) {
7730         SendToProgram("post\n", cps);
7731     }
7732     SendToProgram("hard\n", cps);
7733     if (!appData.ponderNextMove) {
7734         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7735            it without being sure what state we are in first.  "hard"
7736            is not a toggle, so that one is OK.
7737          */
7738         SendToProgram("easy\n", cps);
7739     }
7740     if (cps->usePing) {
7741       sprintf(buf, "ping %d\n", ++cps->lastPing);
7742       SendToProgram(buf, cps);
7743     }
7744     cps->initDone = TRUE;
7745 }
7746
7747
7748 void
7749 StartChessProgram(cps)
7750      ChessProgramState *cps;
7751 {
7752     char buf[MSG_SIZ];
7753     int err;
7754
7755     if (appData.noChessProgram) return;
7756     cps->initDone = FALSE;
7757
7758     if (strcmp(cps->host, "localhost") == 0) {
7759         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7760     } else if (*appData.remoteShell == NULLCHAR) {
7761         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7762     } else {
7763         if (*appData.remoteUser == NULLCHAR) {
7764           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7765                     cps->program);
7766         } else {
7767           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7768                     cps->host, appData.remoteUser, cps->program);
7769         }
7770         err = StartChildProcess(buf, "", &cps->pr);
7771     }
7772
7773     if (err != 0) {
7774         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7775         DisplayFatalError(buf, err, 1);
7776         cps->pr = NoProc;
7777         cps->isr = NULL;
7778         return;
7779     }
7780
7781     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7782     if (cps->protocolVersion > 1) {
7783       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7784       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7785       cps->comboCnt = 0;  //                and values of combo boxes
7786       SendToProgram(buf, cps);
7787     } else {
7788       SendToProgram("xboard\n", cps);
7789     }
7790 }
7791
7792
7793 void
7794 TwoMachinesEventIfReady P((void))
7795 {
7796   if (first.lastPing != first.lastPong) {
7797     DisplayMessage("", _("Waiting for first chess program"));
7798     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7799     return;
7800   }
7801   if (second.lastPing != second.lastPong) {
7802     DisplayMessage("", _("Waiting for second chess program"));
7803     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7804     return;
7805   }
7806   ThawUI();
7807   TwoMachinesEvent();
7808 }
7809
7810 void
7811 NextMatchGame P((void))
7812 {
7813     int index; /* [HGM] autoinc: step lod index during match */
7814     Reset(FALSE, TRUE);
7815     if (*appData.loadGameFile != NULLCHAR) {
7816         index = appData.loadGameIndex;
7817         if(index < 0) { // [HGM] autoinc
7818             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7819             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7820         }
7821         LoadGameFromFile(appData.loadGameFile,
7822                          index,
7823                          appData.loadGameFile, FALSE);
7824     } else if (*appData.loadPositionFile != NULLCHAR) {
7825         index = appData.loadPositionIndex;
7826         if(index < 0) { // [HGM] autoinc
7827             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7828             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7829         }
7830         LoadPositionFromFile(appData.loadPositionFile,
7831                              index,
7832                              appData.loadPositionFile);
7833     }
7834     TwoMachinesEventIfReady();
7835 }
7836
7837 void UserAdjudicationEvent( int result )
7838 {
7839     ChessMove gameResult = GameIsDrawn;
7840
7841     if( result > 0 ) {
7842         gameResult = WhiteWins;
7843     }
7844     else if( result < 0 ) {
7845         gameResult = BlackWins;
7846     }
7847
7848     if( gameMode == TwoMachinesPlay ) {
7849         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7850     }
7851 }
7852
7853
7854 // [HGM] save: calculate checksum of game to make games easily identifiable
7855 int StringCheckSum(char *s)
7856 {
7857         int i = 0;
7858         if(s==NULL) return 0;
7859         while(*s) i = i*259 + *s++;
7860         return i;
7861 }
7862
7863 int GameCheckSum()
7864 {
7865         int i, sum=0;
7866         for(i=backwardMostMove; i<forwardMostMove; i++) {
7867                 sum += pvInfoList[i].depth;
7868                 sum += StringCheckSum(parseList[i]);
7869                 sum += StringCheckSum(commentList[i]);
7870                 sum *= 261;
7871         }
7872         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7873         return sum + StringCheckSum(commentList[i]);
7874 } // end of save patch
7875
7876 void
7877 GameEnds(result, resultDetails, whosays)
7878      ChessMove result;
7879      char *resultDetails;
7880      int whosays;
7881 {
7882     GameMode nextGameMode;
7883     int isIcsGame;
7884     char buf[MSG_SIZ];
7885
7886     if(endingGame) return; /* [HGM] crash: forbid recursion */
7887     endingGame = 1;
7888
7889     if (appData.debugMode) {
7890       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7891               result, resultDetails ? resultDetails : "(null)", whosays);
7892     }
7893
7894     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7895         /* If we are playing on ICS, the server decides when the
7896            game is over, but the engine can offer to draw, claim
7897            a draw, or resign.
7898          */
7899 #if ZIPPY
7900         if (appData.zippyPlay && first.initDone) {
7901             if (result == GameIsDrawn) {
7902                 /* In case draw still needs to be claimed */
7903                 SendToICS(ics_prefix);
7904                 SendToICS("draw\n");
7905             } else if (StrCaseStr(resultDetails, "resign")) {
7906                 SendToICS(ics_prefix);
7907                 SendToICS("resign\n");
7908             }
7909         }
7910 #endif
7911         endingGame = 0; /* [HGM] crash */
7912         return;
7913     }
7914
7915     /* If we're loading the game from a file, stop */
7916     if (whosays == GE_FILE) {
7917       (void) StopLoadGameTimer();
7918       gameFileFP = NULL;
7919     }
7920
7921     /* Cancel draw offers */
7922     first.offeredDraw = second.offeredDraw = 0;
7923
7924     /* If this is an ICS game, only ICS can really say it's done;
7925        if not, anyone can. */
7926     isIcsGame = (gameMode == IcsPlayingWhite ||
7927                  gameMode == IcsPlayingBlack ||
7928                  gameMode == IcsObserving    ||
7929                  gameMode == IcsExamining);
7930
7931     if (!isIcsGame || whosays == GE_ICS) {
7932         /* OK -- not an ICS game, or ICS said it was done */
7933         StopClocks();
7934         if (!isIcsGame && !appData.noChessProgram)
7935           SetUserThinkingEnables();
7936
7937         /* [HGM] if a machine claims the game end we verify this claim */
7938         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7939             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7940                 char claimer;
7941                 ChessMove trueResult = (ChessMove) -1;
7942
7943                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7944                                             first.twoMachinesColor[0] :
7945                                             second.twoMachinesColor[0] ;
7946
7947                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7948                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7949                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7950                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7951                 } else
7952                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7953                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7954                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7955                 } else
7956                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7957                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7958                 }
7959
7960                 // now verify win claims, but not in drop games, as we don't understand those yet
7961                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7962                                                  || gameInfo.variant == VariantGreat) &&
7963                     (result == WhiteWins && claimer == 'w' ||
7964                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7965                       if (appData.debugMode) {
7966                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7967                                 result, epStatus[forwardMostMove], forwardMostMove);
7968                       }
7969                       if(result != trueResult) {
7970                               sprintf(buf, "False win claim: '%s'", resultDetails);
7971                               result = claimer == 'w' ? BlackWins : WhiteWins;
7972                               resultDetails = buf;
7973                       }
7974                 } else
7975                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7976                     && (forwardMostMove <= backwardMostMove ||
7977                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7978                         (claimer=='b')==(forwardMostMove&1))
7979                                                                                   ) {
7980                       /* [HGM] verify: draws that were not flagged are false claims */
7981                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7982                       result = claimer == 'w' ? BlackWins : WhiteWins;
7983                       resultDetails = buf;
7984                 }
7985                 /* (Claiming a loss is accepted no questions asked!) */
7986             }
7987
7988             /* [HGM] bare: don't allow bare King to win */
7989             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7990                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7991                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7992                && result != GameIsDrawn)
7993             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7994                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7995                         int p = (int)boards[forwardMostMove][i][j] - color;
7996                         if(p >= 0 && p <= (int)WhiteKing) k++;
7997                 }
7998                 if (appData.debugMode) {
7999                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8000                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8001                 }
8002                 if(k <= 1) {
8003                         result = GameIsDrawn;
8004                         sprintf(buf, "%s but bare king", resultDetails);
8005                         resultDetails = buf;
8006                 }
8007             }
8008         }
8009
8010         if(serverMoves != NULL && !loadFlag) { char c = '=';
8011             if(result==WhiteWins) c = '+';
8012             if(result==BlackWins) c = '-';
8013             if(resultDetails != NULL)
8014                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8015         }
8016         if (resultDetails != NULL) {
8017             gameInfo.result = result;
8018             gameInfo.resultDetails = StrSave(resultDetails);
8019
8020             /* display last move only if game was not loaded from file */
8021             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8022                 DisplayMove(currentMove - 1);
8023
8024             if (forwardMostMove != 0) {
8025                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8026                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8027                                                                 ) {
8028                     if (*appData.saveGameFile != NULLCHAR) {
8029                         SaveGameToFile(appData.saveGameFile, TRUE);
8030                     } else if (appData.autoSaveGames) {
8031                         AutoSaveGame();
8032                     }
8033                     if (*appData.savePositionFile != NULLCHAR) {
8034                         SavePositionToFile(appData.savePositionFile);
8035                     }
8036                 }
8037             }
8038
8039             /* Tell program how game ended in case it is learning */
8040             /* [HGM] Moved this to after saving the PGN, just in case */
8041             /* engine died and we got here through time loss. In that */
8042             /* case we will get a fatal error writing the pipe, which */
8043             /* would otherwise lose us the PGN.                       */
8044             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8045             /* output during GameEnds should never be fatal anymore   */
8046             if (gameMode == MachinePlaysWhite ||
8047                 gameMode == MachinePlaysBlack ||
8048                 gameMode == TwoMachinesPlay ||
8049                 gameMode == IcsPlayingWhite ||
8050                 gameMode == IcsPlayingBlack ||
8051                 gameMode == BeginningOfGame) {
8052                 char buf[MSG_SIZ];
8053                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8054                         resultDetails);
8055                 if (first.pr != NoProc) {
8056                     SendToProgram(buf, &first);
8057                 }
8058                 if (second.pr != NoProc &&
8059                     gameMode == TwoMachinesPlay) {
8060                     SendToProgram(buf, &second);
8061                 }
8062             }
8063         }
8064
8065         if (appData.icsActive) {
8066             if (appData.quietPlay &&
8067                 (gameMode == IcsPlayingWhite ||
8068                  gameMode == IcsPlayingBlack)) {
8069                 SendToICS(ics_prefix);
8070                 SendToICS("set shout 1\n");
8071             }
8072             nextGameMode = IcsIdle;
8073             ics_user_moved = FALSE;
8074             /* clean up premove.  It's ugly when the game has ended and the
8075              * premove highlights are still on the board.
8076              */
8077             if (gotPremove) {
8078               gotPremove = FALSE;
8079               ClearPremoveHighlights();
8080               DrawPosition(FALSE, boards[currentMove]);
8081             }
8082             if (whosays == GE_ICS) {
8083                 switch (result) {
8084                 case WhiteWins:
8085                     if (gameMode == IcsPlayingWhite)
8086                         PlayIcsWinSound();
8087                     else if(gameMode == IcsPlayingBlack)
8088                         PlayIcsLossSound();
8089                     break;
8090                 case BlackWins:
8091                     if (gameMode == IcsPlayingBlack)
8092                         PlayIcsWinSound();
8093                     else if(gameMode == IcsPlayingWhite)
8094                         PlayIcsLossSound();
8095                     break;
8096                 case GameIsDrawn:
8097                     PlayIcsDrawSound();
8098                     break;
8099                 default:
8100                     PlayIcsUnfinishedSound();
8101                 }
8102             }
8103         } else if (gameMode == EditGame ||
8104                    gameMode == PlayFromGameFile ||
8105                    gameMode == AnalyzeMode ||
8106                    gameMode == AnalyzeFile) {
8107             nextGameMode = gameMode;
8108         } else {
8109             nextGameMode = EndOfGame;
8110         }
8111         pausing = FALSE;
8112         ModeHighlight();
8113     } else {
8114         nextGameMode = gameMode;
8115     }
8116
8117     if (appData.noChessProgram) {
8118         gameMode = nextGameMode;
8119         ModeHighlight();
8120         endingGame = 0; /* [HGM] crash */
8121         return;
8122     }
8123
8124     if (first.reuse) {
8125         /* Put first chess program into idle state */
8126         if (first.pr != NoProc &&
8127             (gameMode == MachinePlaysWhite ||
8128              gameMode == MachinePlaysBlack ||
8129              gameMode == TwoMachinesPlay ||
8130              gameMode == IcsPlayingWhite ||
8131              gameMode == IcsPlayingBlack ||
8132              gameMode == BeginningOfGame)) {
8133             SendToProgram("force\n", &first);
8134             if (first.usePing) {
8135               char buf[MSG_SIZ];
8136               sprintf(buf, "ping %d\n", ++first.lastPing);
8137               SendToProgram(buf, &first);
8138             }
8139         }
8140     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8141         /* Kill off first chess program */
8142         if (first.isr != NULL)
8143           RemoveInputSource(first.isr);
8144         first.isr = NULL;
8145
8146         if (first.pr != NoProc) {
8147             ExitAnalyzeMode();
8148             DoSleep( appData.delayBeforeQuit );
8149             SendToProgram("quit\n", &first);
8150             DoSleep( appData.delayAfterQuit );
8151             DestroyChildProcess(first.pr, first.useSigterm);
8152         }
8153         first.pr = NoProc;
8154     }
8155     if (second.reuse) {
8156         /* Put second chess program into idle state */
8157         if (second.pr != NoProc &&
8158             gameMode == TwoMachinesPlay) {
8159             SendToProgram("force\n", &second);
8160             if (second.usePing) {
8161               char buf[MSG_SIZ];
8162               sprintf(buf, "ping %d\n", ++second.lastPing);
8163               SendToProgram(buf, &second);
8164             }
8165         }
8166     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8167         /* Kill off second chess program */
8168         if (second.isr != NULL)
8169           RemoveInputSource(second.isr);
8170         second.isr = NULL;
8171
8172         if (second.pr != NoProc) {
8173             DoSleep( appData.delayBeforeQuit );
8174             SendToProgram("quit\n", &second);
8175             DoSleep( appData.delayAfterQuit );
8176             DestroyChildProcess(second.pr, second.useSigterm);
8177         }
8178         second.pr = NoProc;
8179     }
8180
8181     if (matchMode && gameMode == TwoMachinesPlay) {
8182         switch (result) {
8183         case WhiteWins:
8184           if (first.twoMachinesColor[0] == 'w') {
8185             first.matchWins++;
8186           } else {
8187             second.matchWins++;
8188           }
8189           break;
8190         case BlackWins:
8191           if (first.twoMachinesColor[0] == 'b') {
8192             first.matchWins++;
8193           } else {
8194             second.matchWins++;
8195           }
8196           break;
8197         default:
8198           break;
8199         }
8200         if (matchGame < appData.matchGames) {
8201             char *tmp;
8202             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8203                 tmp = first.twoMachinesColor;
8204                 first.twoMachinesColor = second.twoMachinesColor;
8205                 second.twoMachinesColor = tmp;
8206             }
8207             gameMode = nextGameMode;
8208             matchGame++;
8209             if(appData.matchPause>10000 || appData.matchPause<10)
8210                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8211             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8212             endingGame = 0; /* [HGM] crash */
8213             return;
8214         } else {
8215             char buf[MSG_SIZ];
8216             gameMode = nextGameMode;
8217             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8218                     first.tidy, second.tidy,
8219                     first.matchWins, second.matchWins,
8220                     appData.matchGames - (first.matchWins + second.matchWins));
8221             DisplayFatalError(buf, 0, 0);
8222         }
8223     }
8224     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8225         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8226       ExitAnalyzeMode();
8227     gameMode = nextGameMode;
8228     ModeHighlight();
8229     endingGame = 0;  /* [HGM] crash */
8230 }
8231
8232 /* Assumes program was just initialized (initString sent).
8233    Leaves program in force mode. */
8234 void
8235 FeedMovesToProgram(cps, upto)
8236      ChessProgramState *cps;
8237      int upto;
8238 {
8239     int i;
8240
8241     if (appData.debugMode)
8242       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8243               startedFromSetupPosition ? "position and " : "",
8244               backwardMostMove, upto, cps->which);
8245     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8246         // [HGM] variantswitch: make engine aware of new variant
8247         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8248                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8249         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8250         SendToProgram(buf, cps);
8251         currentlyInitializedVariant = gameInfo.variant;
8252     }
8253     SendToProgram("force\n", cps);
8254     if (startedFromSetupPosition) {
8255         SendBoard(cps, backwardMostMove);
8256     if (appData.debugMode) {
8257         fprintf(debugFP, "feedMoves\n");
8258     }
8259     }
8260     for (i = backwardMostMove; i < upto; i++) {
8261         SendMoveToProgram(i, cps);
8262     }
8263 }
8264
8265
8266 void
8267 ResurrectChessProgram()
8268 {
8269      /* The chess program may have exited.
8270         If so, restart it and feed it all the moves made so far. */
8271
8272     if (appData.noChessProgram || first.pr != NoProc) return;
8273
8274     StartChessProgram(&first);
8275     InitChessProgram(&first, FALSE);
8276     FeedMovesToProgram(&first, currentMove);
8277
8278     if (!first.sendTime) {
8279         /* can't tell gnuchess what its clock should read,
8280            so we bow to its notion. */
8281         ResetClocks();
8282         timeRemaining[0][currentMove] = whiteTimeRemaining;
8283         timeRemaining[1][currentMove] = blackTimeRemaining;
8284     }
8285
8286     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8287                 appData.icsEngineAnalyze) && first.analysisSupport) {
8288       SendToProgram("analyze\n", &first);
8289       first.analyzing = TRUE;
8290     }
8291 }
8292
8293 /*
8294  * Button procedures
8295  */
8296 void
8297 Reset(redraw, init)
8298      int redraw, init;
8299 {
8300     int i;
8301
8302     if (appData.debugMode) {
8303         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8304                 redraw, init, gameMode);
8305     }
8306     pausing = pauseExamInvalid = FALSE;
8307     startedFromSetupPosition = blackPlaysFirst = FALSE;
8308     firstMove = TRUE;
8309     whiteFlag = blackFlag = FALSE;
8310     userOfferedDraw = FALSE;
8311     hintRequested = bookRequested = FALSE;
8312     first.maybeThinking = FALSE;
8313     second.maybeThinking = FALSE;
8314     first.bookSuspend = FALSE; // [HGM] book
8315     second.bookSuspend = FALSE;
8316     thinkOutput[0] = NULLCHAR;
8317     lastHint[0] = NULLCHAR;
8318     ClearGameInfo(&gameInfo);
8319     gameInfo.variant = StringToVariant(appData.variant);
8320     ics_user_moved = ics_clock_paused = FALSE;
8321     ics_getting_history = H_FALSE;
8322     ics_gamenum = -1;
8323     white_holding[0] = black_holding[0] = NULLCHAR;
8324     ClearProgramStats();
8325     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8326
8327     ResetFrontEnd();
8328     ClearHighlights();
8329     flipView = appData.flipView;
8330     ClearPremoveHighlights();
8331     gotPremove = FALSE;
8332     alarmSounded = FALSE;
8333
8334     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8335     if(appData.serverMovesName != NULL) {
8336         /* [HGM] prepare to make moves file for broadcasting */
8337         clock_t t = clock();
8338         if(serverMoves != NULL) fclose(serverMoves);
8339         serverMoves = fopen(appData.serverMovesName, "r");
8340         if(serverMoves != NULL) {
8341             fclose(serverMoves);
8342             /* delay 15 sec before overwriting, so all clients can see end */
8343             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8344         }
8345         serverMoves = fopen(appData.serverMovesName, "w");
8346     }
8347
8348     ExitAnalyzeMode();
8349     gameMode = BeginningOfGame;
8350     ModeHighlight();
8351
8352     if(appData.icsActive) gameInfo.variant = VariantNormal;
8353     currentMove = forwardMostMove = backwardMostMove = 0;
8354     InitPosition(redraw);
8355     for (i = 0; i < MAX_MOVES; i++) {
8356         if (commentList[i] != NULL) {
8357             free(commentList[i]);
8358             commentList[i] = NULL;
8359         }
8360     }
8361
8362     ResetClocks();
8363     timeRemaining[0][0] = whiteTimeRemaining;
8364     timeRemaining[1][0] = blackTimeRemaining;
8365     if (first.pr == NULL) {
8366         StartChessProgram(&first);
8367     }
8368     if (init) {
8369             InitChessProgram(&first, startedFromSetupPosition);
8370     }
8371
8372     DisplayTitle("");
8373     DisplayMessage("", "");
8374     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8375     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8376     return;
8377 }
8378
8379 void
8380 AutoPlayGameLoop()
8381 {
8382     for (;;) {
8383         if (!AutoPlayOneMove())
8384           return;
8385         if (matchMode || appData.timeDelay == 0)
8386           continue;
8387         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8388           return;
8389         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8390         break;
8391     }
8392 }
8393
8394
8395 int
8396 AutoPlayOneMove()
8397 {
8398     int fromX, fromY, toX, toY;
8399
8400     if (appData.debugMode) {
8401       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8402     }
8403
8404     if (gameMode != PlayFromGameFile)
8405       return FALSE;
8406
8407     if (currentMove >= forwardMostMove) {
8408       gameMode = EditGame;
8409       ModeHighlight();
8410
8411       /* [AS] Clear current move marker at the end of a game */
8412       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8413
8414       return FALSE;
8415     }
8416
8417     toX = moveList[currentMove][2] - AAA;
8418     toY = moveList[currentMove][3] - ONE;
8419
8420     if (moveList[currentMove][1] == '@') {
8421         if (appData.highlightLastMove) {
8422             SetHighlights(-1, -1, toX, toY);
8423         }
8424     } else {
8425         fromX = moveList[currentMove][0] - AAA;
8426         fromY = moveList[currentMove][1] - ONE;
8427
8428         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8429
8430         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8431
8432         if (appData.highlightLastMove) {
8433             SetHighlights(fromX, fromY, toX, toY);
8434         }
8435     }
8436     DisplayMove(currentMove);
8437     SendMoveToProgram(currentMove++, &first);
8438     DisplayBothClocks();
8439     DrawPosition(FALSE, boards[currentMove]);
8440     // [HGM] PV info: always display, routine tests if empty
8441     DisplayComment(currentMove - 1, commentList[currentMove]);
8442     return TRUE;
8443 }
8444
8445
8446 int
8447 LoadGameOneMove(readAhead)
8448      ChessMove readAhead;
8449 {
8450     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8451     char promoChar = NULLCHAR;
8452     ChessMove moveType;
8453     char move[MSG_SIZ];
8454     char *p, *q;
8455
8456     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8457         gameMode != AnalyzeMode && gameMode != Training) {
8458         gameFileFP = NULL;
8459         return FALSE;
8460     }
8461
8462     yyboardindex = forwardMostMove;
8463     if (readAhead != (ChessMove)0) {
8464       moveType = readAhead;
8465     } else {
8466       if (gameFileFP == NULL)
8467           return FALSE;
8468       moveType = (ChessMove) yylex();
8469     }
8470
8471     done = FALSE;
8472     switch (moveType) {
8473       case Comment:
8474         if (appData.debugMode)
8475           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8476         p = yy_text;
8477         if (*p == '{' || *p == '[' || *p == '(') {
8478             p[strlen(p) - 1] = NULLCHAR;
8479             p++;
8480         }
8481
8482         /* append the comment but don't display it */
8483         while (*p == '\n') p++;
8484         AppendComment(currentMove, p);
8485         return TRUE;
8486
8487       case WhiteCapturesEnPassant:
8488       case BlackCapturesEnPassant:
8489       case WhitePromotionChancellor:
8490       case BlackPromotionChancellor:
8491       case WhitePromotionArchbishop:
8492       case BlackPromotionArchbishop:
8493       case WhitePromotionCentaur:
8494       case BlackPromotionCentaur:
8495       case WhitePromotionQueen:
8496       case BlackPromotionQueen:
8497       case WhitePromotionRook:
8498       case BlackPromotionRook:
8499       case WhitePromotionBishop:
8500       case BlackPromotionBishop:
8501       case WhitePromotionKnight:
8502       case BlackPromotionKnight:
8503       case WhitePromotionKing:
8504       case BlackPromotionKing:
8505       case NormalMove:
8506       case WhiteKingSideCastle:
8507       case WhiteQueenSideCastle:
8508       case BlackKingSideCastle:
8509       case BlackQueenSideCastle:
8510       case WhiteKingSideCastleWild:
8511       case WhiteQueenSideCastleWild:
8512       case BlackKingSideCastleWild:
8513       case BlackQueenSideCastleWild:
8514       /* PUSH Fabien */
8515       case WhiteHSideCastleFR:
8516       case WhiteASideCastleFR:
8517       case BlackHSideCastleFR:
8518       case BlackASideCastleFR:
8519       /* POP Fabien */
8520         if (appData.debugMode)
8521           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8522         fromX = currentMoveString[0] - AAA;
8523         fromY = currentMoveString[1] - ONE;
8524         toX = currentMoveString[2] - AAA;
8525         toY = currentMoveString[3] - ONE;
8526         promoChar = currentMoveString[4];
8527         break;
8528
8529       case WhiteDrop:
8530       case BlackDrop:
8531         if (appData.debugMode)
8532           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8533         fromX = moveType == WhiteDrop ?
8534           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8535         (int) CharToPiece(ToLower(currentMoveString[0]));
8536         fromY = DROP_RANK;
8537         toX = currentMoveString[2] - AAA;
8538         toY = currentMoveString[3] - ONE;
8539         break;
8540
8541       case WhiteWins:
8542       case BlackWins:
8543       case GameIsDrawn:
8544       case GameUnfinished:
8545         if (appData.debugMode)
8546           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8547         p = strchr(yy_text, '{');
8548         if (p == NULL) p = strchr(yy_text, '(');
8549         if (p == NULL) {
8550             p = yy_text;
8551             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8552         } else {
8553             q = strchr(p, *p == '{' ? '}' : ')');
8554             if (q != NULL) *q = NULLCHAR;
8555             p++;
8556         }
8557         GameEnds(moveType, p, GE_FILE);
8558         done = TRUE;
8559         if (cmailMsgLoaded) {
8560             ClearHighlights();
8561             flipView = WhiteOnMove(currentMove);
8562             if (moveType == GameUnfinished) flipView = !flipView;
8563             if (appData.debugMode)
8564               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8565         }
8566         break;
8567
8568       case (ChessMove) 0:       /* end of file */
8569         if (appData.debugMode)
8570           fprintf(debugFP, "Parser hit end of file\n");
8571         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8572                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8573           case MT_NONE:
8574           case MT_CHECK:
8575             break;
8576           case MT_CHECKMATE:
8577           case MT_STAINMATE:
8578             if (WhiteOnMove(currentMove)) {
8579                 GameEnds(BlackWins, "Black mates", GE_FILE);
8580             } else {
8581                 GameEnds(WhiteWins, "White mates", GE_FILE);
8582             }
8583             break;
8584           case MT_STALEMATE:
8585             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8586             break;
8587         }
8588         done = TRUE;
8589         break;
8590
8591       case MoveNumberOne:
8592         if (lastLoadGameStart == GNUChessGame) {
8593             /* GNUChessGames have numbers, but they aren't move numbers */
8594             if (appData.debugMode)
8595               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8596                       yy_text, (int) moveType);
8597             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8598         }
8599         /* else fall thru */
8600
8601       case XBoardGame:
8602       case GNUChessGame:
8603       case PGNTag:
8604         /* Reached start of next game in file */
8605         if (appData.debugMode)
8606           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8607         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8608                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8609           case MT_NONE:
8610           case MT_CHECK:
8611             break;
8612           case MT_CHECKMATE:
8613           case MT_STAINMATE:
8614             if (WhiteOnMove(currentMove)) {
8615                 GameEnds(BlackWins, "Black mates", GE_FILE);
8616             } else {
8617                 GameEnds(WhiteWins, "White mates", GE_FILE);
8618             }
8619             break;
8620           case MT_STALEMATE:
8621             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8622             break;
8623         }
8624         done = TRUE;
8625         break;
8626
8627       case PositionDiagram:     /* should not happen; ignore */
8628       case ElapsedTime:         /* ignore */
8629       case NAG:                 /* ignore */
8630         if (appData.debugMode)
8631           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8632                   yy_text, (int) moveType);
8633         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8634
8635       case IllegalMove:
8636         if (appData.testLegality) {
8637             if (appData.debugMode)
8638               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8639             sprintf(move, _("Illegal move: %d.%s%s"),
8640                     (forwardMostMove / 2) + 1,
8641                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8642             DisplayError(move, 0);
8643             done = TRUE;
8644         } else {
8645             if (appData.debugMode)
8646               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8647                       yy_text, currentMoveString);
8648             fromX = currentMoveString[0] - AAA;
8649             fromY = currentMoveString[1] - ONE;
8650             toX = currentMoveString[2] - AAA;
8651             toY = currentMoveString[3] - ONE;
8652             promoChar = currentMoveString[4];
8653         }
8654         break;
8655
8656       case AmbiguousMove:
8657         if (appData.debugMode)
8658           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8659         sprintf(move, _("Ambiguous move: %d.%s%s"),
8660                 (forwardMostMove / 2) + 1,
8661                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8662         DisplayError(move, 0);
8663         done = TRUE;
8664         break;
8665
8666       default:
8667       case ImpossibleMove:
8668         if (appData.debugMode)
8669           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8670         sprintf(move, _("Illegal move: %d.%s%s"),
8671                 (forwardMostMove / 2) + 1,
8672                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8673         DisplayError(move, 0);
8674         done = TRUE;
8675         break;
8676     }
8677
8678     if (done) {
8679         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8680             DrawPosition(FALSE, boards[currentMove]);
8681             DisplayBothClocks();
8682             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8683               DisplayComment(currentMove - 1, commentList[currentMove]);
8684         }
8685         (void) StopLoadGameTimer();
8686         gameFileFP = NULL;
8687         cmailOldMove = forwardMostMove;
8688         return FALSE;
8689     } else {
8690         /* currentMoveString is set as a side-effect of yylex */
8691         strcat(currentMoveString, "\n");
8692         strcpy(moveList[forwardMostMove], currentMoveString);
8693
8694         thinkOutput[0] = NULLCHAR;
8695         MakeMove(fromX, fromY, toX, toY, promoChar);
8696         currentMove = forwardMostMove;
8697         return TRUE;
8698     }
8699 }
8700
8701 /* Load the nth game from the given file */
8702 int
8703 LoadGameFromFile(filename, n, title, useList)
8704      char *filename;
8705      int n;
8706      char *title;
8707      /*Boolean*/ int useList;
8708 {
8709     FILE *f;
8710     char buf[MSG_SIZ];
8711
8712     if (strcmp(filename, "-") == 0) {
8713         f = stdin;
8714         title = "stdin";
8715     } else {
8716         f = fopen(filename, "rb");
8717         if (f == NULL) {
8718           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8719             DisplayError(buf, errno);
8720             return FALSE;
8721         }
8722     }
8723     if (fseek(f, 0, 0) == -1) {
8724         /* f is not seekable; probably a pipe */
8725         useList = FALSE;
8726     }
8727     if (useList && n == 0) {
8728         int error = GameListBuild(f);
8729         if (error) {
8730             DisplayError(_("Cannot build game list"), error);
8731         } else if (!ListEmpty(&gameList) &&
8732                    ((ListGame *) gameList.tailPred)->number > 1) {
8733             GameListPopUp(f, title);
8734             return TRUE;
8735         }
8736         GameListDestroy();
8737         n = 1;
8738     }
8739     if (n == 0) n = 1;
8740     return LoadGame(f, n, title, FALSE);
8741 }
8742
8743
8744 void
8745 MakeRegisteredMove()
8746 {
8747     int fromX, fromY, toX, toY;
8748     char promoChar;
8749     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8750         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8751           case CMAIL_MOVE:
8752           case CMAIL_DRAW:
8753             if (appData.debugMode)
8754               fprintf(debugFP, "Restoring %s for game %d\n",
8755                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8756
8757             thinkOutput[0] = NULLCHAR;
8758             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8759             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8760             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8761             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8762             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8763             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8764             MakeMove(fromX, fromY, toX, toY, promoChar);
8765             ShowMove(fromX, fromY, toX, toY);
8766
8767             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8768                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8769               case MT_NONE:
8770               case MT_CHECK:
8771                 break;
8772
8773               case MT_CHECKMATE:
8774               case MT_STAINMATE:
8775                 if (WhiteOnMove(currentMove)) {
8776                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8777                 } else {
8778                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8779                 }
8780                 break;
8781
8782               case MT_STALEMATE:
8783                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8784                 break;
8785             }
8786
8787             break;
8788
8789           case CMAIL_RESIGN:
8790             if (WhiteOnMove(currentMove)) {
8791                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8792             } else {
8793                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8794             }
8795             break;
8796
8797           case CMAIL_ACCEPT:
8798             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8799             break;
8800
8801           default:
8802             break;
8803         }
8804     }
8805
8806     return;
8807 }
8808
8809 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8810 int
8811 CmailLoadGame(f, gameNumber, title, useList)
8812      FILE *f;
8813      int gameNumber;
8814      char *title;
8815      int useList;
8816 {
8817     int retVal;
8818
8819     if (gameNumber > nCmailGames) {
8820         DisplayError(_("No more games in this message"), 0);
8821         return FALSE;
8822     }
8823     if (f == lastLoadGameFP) {
8824         int offset = gameNumber - lastLoadGameNumber;
8825         if (offset == 0) {
8826             cmailMsg[0] = NULLCHAR;
8827             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8828                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8829                 nCmailMovesRegistered--;
8830             }
8831             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8832             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8833                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8834             }
8835         } else {
8836             if (! RegisterMove()) return FALSE;
8837         }
8838     }
8839
8840     retVal = LoadGame(f, gameNumber, title, useList);
8841
8842     /* Make move registered during previous look at this game, if any */
8843     MakeRegisteredMove();
8844
8845     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8846         commentList[currentMove]
8847           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8848         DisplayComment(currentMove - 1, commentList[currentMove]);
8849     }
8850
8851     return retVal;
8852 }
8853
8854 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8855 int
8856 ReloadGame(offset)
8857      int offset;
8858 {
8859     int gameNumber = lastLoadGameNumber + offset;
8860     if (lastLoadGameFP == NULL) {
8861         DisplayError(_("No game has been loaded yet"), 0);
8862         return FALSE;
8863     }
8864     if (gameNumber <= 0) {
8865         DisplayError(_("Can't back up any further"), 0);
8866         return FALSE;
8867     }
8868     if (cmailMsgLoaded) {
8869         return CmailLoadGame(lastLoadGameFP, gameNumber,
8870                              lastLoadGameTitle, lastLoadGameUseList);
8871     } else {
8872         return LoadGame(lastLoadGameFP, gameNumber,
8873                         lastLoadGameTitle, lastLoadGameUseList);
8874     }
8875 }
8876
8877
8878
8879 /* Load the nth game from open file f */
8880 int
8881 LoadGame(f, gameNumber, title, useList)
8882      FILE *f;
8883      int gameNumber;
8884      char *title;
8885      int useList;
8886 {
8887     ChessMove cm;
8888     char buf[MSG_SIZ];
8889     int gn = gameNumber;
8890     ListGame *lg = NULL;
8891     int numPGNTags = 0;
8892     int err;
8893     GameMode oldGameMode;
8894     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8895
8896     if (appData.debugMode)
8897         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8898
8899     if (gameMode == Training )
8900         SetTrainingModeOff();
8901
8902     oldGameMode = gameMode;
8903     if (gameMode != BeginningOfGame) 
8904       {
8905         Reset(FALSE, TRUE);
8906       };
8907
8908     gameFileFP = f;
8909     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
8910       {
8911         fclose(lastLoadGameFP);
8912       };
8913
8914     if (useList) 
8915       {
8916         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8917         
8918         if (lg) 
8919           {
8920             fseek(f, lg->offset, 0);
8921             GameListHighlight(gameNumber);
8922             gn = 1;
8923           }
8924         else 
8925           {
8926             DisplayError(_("Game number out of range"), 0);
8927             return FALSE;
8928           };
8929       } 
8930     else 
8931       {
8932         GameListDestroy();
8933         if (fseek(f, 0, 0) == -1) 
8934           {
8935             if (f == lastLoadGameFP ?
8936                 gameNumber == lastLoadGameNumber + 1 :
8937                 gameNumber == 1) 
8938               {
8939                 gn = 1;
8940               } 
8941             else 
8942               {
8943                 DisplayError(_("Can't seek on game file"), 0);
8944                 return FALSE;
8945               };
8946           };
8947       };
8948
8949     lastLoadGameFP      = f;
8950     lastLoadGameNumber  = gameNumber;
8951     strcpy(lastLoadGameTitle, title);
8952     lastLoadGameUseList = useList;
8953
8954     yynewfile(f);
8955
8956     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
8957       {
8958         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8959                  lg->gameInfo.black);
8960         DisplayTitle(buf);
8961       } 
8962     else if (*title != NULLCHAR) 
8963       {
8964         if (gameNumber > 1) 
8965           {
8966             sprintf(buf, "%s %d", title, gameNumber);
8967             DisplayTitle(buf);
8968           } 
8969         else 
8970           {
8971             DisplayTitle(title);
8972           };
8973       };
8974
8975     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
8976       {
8977         gameMode = PlayFromGameFile;
8978         ModeHighlight();
8979       };
8980
8981     currentMove = forwardMostMove = backwardMostMove = 0;
8982     CopyBoard(boards[0], initialPosition);
8983     StopClocks();
8984
8985     /*
8986      * Skip the first gn-1 games in the file.
8987      * Also skip over anything that precedes an identifiable
8988      * start of game marker, to avoid being confused by
8989      * garbage at the start of the file.  Currently
8990      * recognized start of game markers are the move number "1",
8991      * the pattern "gnuchess .* game", the pattern
8992      * "^[#;%] [^ ]* game file", and a PGN tag block.
8993      * A game that starts with one of the latter two patterns
8994      * will also have a move number 1, possibly
8995      * following a position diagram.
8996      * 5-4-02: Let's try being more lenient and allowing a game to
8997      * start with an unnumbered move.  Does that break anything?
8998      */
8999     cm = lastLoadGameStart = (ChessMove) 0;
9000     while (gn > 0) {
9001         yyboardindex = forwardMostMove;
9002         cm = (ChessMove) yylex();
9003         switch (cm) {
9004           case (ChessMove) 0:
9005             if (cmailMsgLoaded) {
9006                 nCmailGames = CMAIL_MAX_GAMES - gn;
9007             } else {
9008                 Reset(TRUE, TRUE);
9009                 DisplayError(_("Game not found in file"), 0);
9010             }
9011             return FALSE;
9012
9013           case GNUChessGame:
9014           case XBoardGame:
9015             gn--;
9016             lastLoadGameStart = cm;
9017             break;
9018
9019           case MoveNumberOne:
9020             switch (lastLoadGameStart) {
9021               case GNUChessGame:
9022               case XBoardGame:
9023               case PGNTag:
9024                 break;
9025               case MoveNumberOne:
9026               case (ChessMove) 0:
9027                 gn--;           /* count this game */
9028                 lastLoadGameStart = cm;
9029                 break;
9030               default:
9031                 /* impossible */
9032                 break;
9033             }
9034             break;
9035
9036           case PGNTag:
9037             switch (lastLoadGameStart) {
9038               case GNUChessGame:
9039               case PGNTag:
9040               case MoveNumberOne:
9041               case (ChessMove) 0:
9042                 gn--;           /* count this game */
9043                 lastLoadGameStart = cm;
9044                 break;
9045               case XBoardGame:
9046                 lastLoadGameStart = cm; /* game counted already */
9047                 break;
9048               default:
9049                 /* impossible */
9050                 break;
9051             }
9052             if (gn > 0) {
9053                 do {
9054                     yyboardindex = forwardMostMove;
9055                     cm = (ChessMove) yylex();
9056                 } while (cm == PGNTag || cm == Comment);
9057             }
9058             break;
9059
9060           case WhiteWins:
9061           case BlackWins:
9062           case GameIsDrawn:
9063             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9064                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9065                     != CMAIL_OLD_RESULT) {
9066                     nCmailResults ++ ;
9067                     cmailResult[  CMAIL_MAX_GAMES
9068                                 - gn - 1] = CMAIL_OLD_RESULT;
9069                 }
9070             }
9071             break;
9072
9073           case NormalMove:
9074             /* Only a NormalMove can be at the start of a game
9075              * without a position diagram. */
9076             if (lastLoadGameStart == (ChessMove) 0) {
9077               gn--;
9078               lastLoadGameStart = MoveNumberOne;
9079             }
9080             break;
9081
9082           default:
9083             break;
9084         }
9085     }
9086
9087     if (appData.debugMode)
9088       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9089
9090     if (cm == XBoardGame) {
9091         /* Skip any header junk before position diagram and/or move 1 */
9092         for (;;) {
9093             yyboardindex = forwardMostMove;
9094             cm = (ChessMove) yylex();
9095
9096             if (cm == (ChessMove) 0 ||
9097                 cm == GNUChessGame || cm == XBoardGame) {
9098                 /* Empty game; pretend end-of-file and handle later */
9099                 cm = (ChessMove) 0;
9100                 break;
9101             }
9102
9103             if (cm == MoveNumberOne || cm == PositionDiagram ||
9104                 cm == PGNTag || cm == Comment)
9105               break;
9106         }
9107     } else if (cm == GNUChessGame) {
9108         if (gameInfo.event != NULL) {
9109             free(gameInfo.event);
9110         }
9111         gameInfo.event = StrSave(yy_text);
9112     }
9113
9114     startedFromSetupPosition = FALSE;
9115     while (cm == PGNTag) {
9116         if (appData.debugMode)
9117           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9118         err = ParsePGNTag(yy_text, &gameInfo);
9119         if (!err) numPGNTags++;
9120
9121         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9122         if(gameInfo.variant != oldVariant) {
9123             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9124             InitPosition(TRUE);
9125             oldVariant = gameInfo.variant;
9126             if (appData.debugMode)
9127               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9128         }
9129
9130
9131         if (gameInfo.fen != NULL) {
9132           Board initial_position;
9133           startedFromSetupPosition = TRUE;
9134           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9135             Reset(TRUE, TRUE);
9136             DisplayError(_("Bad FEN position in file"), 0);
9137             return FALSE;
9138           }
9139           CopyBoard(boards[0], initial_position);
9140           if (blackPlaysFirst) {
9141             currentMove = forwardMostMove = backwardMostMove = 1;
9142             CopyBoard(boards[1], initial_position);
9143             strcpy(moveList[0], "");
9144             strcpy(parseList[0], "");
9145             timeRemaining[0][1] = whiteTimeRemaining;
9146             timeRemaining[1][1] = blackTimeRemaining;
9147             if (commentList[0] != NULL) {
9148               commentList[1] = commentList[0];
9149               commentList[0] = NULL;
9150             }
9151           } else {
9152             currentMove = forwardMostMove = backwardMostMove = 0;
9153           }
9154           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9155           {   int i;
9156               initialRulePlies = FENrulePlies;
9157               epStatus[forwardMostMove] = FENepStatus;
9158               for( i=0; i< nrCastlingRights; i++ )
9159                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9160           }
9161           yyboardindex = forwardMostMove;
9162           free(gameInfo.fen);
9163           gameInfo.fen = NULL;
9164         }
9165
9166         yyboardindex = forwardMostMove;
9167         cm = (ChessMove) yylex();
9168
9169         /* Handle comments interspersed among the tags */
9170         while (cm == Comment) {
9171             char *p;
9172             if (appData.debugMode)
9173               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9174             p = yy_text;
9175             if (*p == '{' || *p == '[' || *p == '(') {
9176                 p[strlen(p) - 1] = NULLCHAR;
9177                 p++;
9178             }
9179             while (*p == '\n') p++;
9180             AppendComment(currentMove, p);
9181             yyboardindex = forwardMostMove;
9182             cm = (ChessMove) yylex();
9183         }
9184     }
9185
9186     /* don't rely on existence of Event tag since if game was
9187      * pasted from clipboard the Event tag may not exist
9188      */
9189     if (numPGNTags > 0){
9190         char *tags;
9191         if (gameInfo.variant == VariantNormal) {
9192           gameInfo.variant = StringToVariant(gameInfo.event);
9193         }
9194         if (!matchMode) {
9195           if( appData.autoDisplayTags ) {
9196             tags = PGNTags(&gameInfo);
9197             TagsPopUp(tags, CmailMsg());
9198             free(tags);
9199           }
9200         }
9201     } else {
9202         /* Make something up, but don't display it now */
9203         SetGameInfo();
9204         TagsPopDown();
9205     }
9206
9207     if (cm == PositionDiagram) {
9208         int i, j;
9209         char *p;
9210         Board initial_position;
9211
9212         if (appData.debugMode)
9213           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9214
9215         if (!startedFromSetupPosition) {
9216             p = yy_text;
9217             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9218               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9219                 switch (*p) {
9220                   case '[':
9221                   case '-':
9222                   case ' ':
9223                   case '\t':
9224                   case '\n':
9225                   case '\r':
9226                     break;
9227                   default:
9228                     initial_position[i][j++] = CharToPiece(*p);
9229                     break;
9230                 }
9231             while (*p == ' ' || *p == '\t' ||
9232                    *p == '\n' || *p == '\r') p++;
9233
9234             if (strncmp(p, "black", strlen("black"))==0)
9235               blackPlaysFirst = TRUE;
9236             else
9237               blackPlaysFirst = FALSE;
9238             startedFromSetupPosition = TRUE;
9239
9240             CopyBoard(boards[0], initial_position);
9241             if (blackPlaysFirst) {
9242                 currentMove = forwardMostMove = backwardMostMove = 1;
9243                 CopyBoard(boards[1], initial_position);
9244                 strcpy(moveList[0], "");
9245                 strcpy(parseList[0], "");
9246                 timeRemaining[0][1] = whiteTimeRemaining;
9247                 timeRemaining[1][1] = blackTimeRemaining;
9248                 if (commentList[0] != NULL) {
9249                     commentList[1] = commentList[0];
9250                     commentList[0] = NULL;
9251                 }
9252             } else {
9253                 currentMove = forwardMostMove = backwardMostMove = 0;
9254             }
9255         }
9256         yyboardindex = forwardMostMove;
9257         cm = (ChessMove) yylex();
9258     }
9259
9260     if (first.pr == NoProc) {
9261         StartChessProgram(&first);
9262     }
9263     InitChessProgram(&first, FALSE);
9264     SendToProgram("force\n", &first);
9265     if (startedFromSetupPosition) {
9266         SendBoard(&first, forwardMostMove);
9267     if (appData.debugMode) {
9268         fprintf(debugFP, "Load Game\n");
9269     }
9270         DisplayBothClocks();
9271     }
9272
9273     /* [HGM] server: flag to write setup moves in broadcast file as one */
9274     loadFlag = appData.suppressLoadMoves;
9275
9276     while (cm == Comment) {
9277         char *p;
9278         if (appData.debugMode)
9279           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9280         p = yy_text;
9281         if (*p == '{' || *p == '[' || *p == '(') {
9282             p[strlen(p) - 1] = NULLCHAR;
9283             p++;
9284         }
9285         while (*p == '\n') p++;
9286         AppendComment(currentMove, p);
9287         yyboardindex = forwardMostMove;
9288         cm = (ChessMove) yylex();
9289     }
9290
9291     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9292         cm == WhiteWins || cm == BlackWins ||
9293         cm == GameIsDrawn || cm == GameUnfinished) {
9294         DisplayMessage("", _("No moves in game"));
9295         if (cmailMsgLoaded) {
9296             if (appData.debugMode)
9297               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9298             ClearHighlights();
9299             flipView = FALSE;
9300         }
9301         DrawPosition(FALSE, boards[currentMove]);
9302         DisplayBothClocks();
9303         gameMode = EditGame;
9304         ModeHighlight();
9305         gameFileFP = NULL;
9306         cmailOldMove = 0;
9307         return TRUE;
9308     }
9309
9310     // [HGM] PV info: routine tests if comment empty
9311     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9312         DisplayComment(currentMove - 1, commentList[currentMove]);
9313     }
9314     if (!matchMode && appData.timeDelay != 0)
9315       DrawPosition(FALSE, boards[currentMove]);
9316
9317     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9318       programStats.ok_to_send = 1;
9319     }
9320
9321     /* if the first token after the PGN tags is a move
9322      * and not move number 1, retrieve it from the parser
9323      */
9324     if (cm != MoveNumberOne)
9325         LoadGameOneMove(cm);
9326
9327     /* load the remaining moves from the file */
9328     while (LoadGameOneMove((ChessMove)0)) {
9329       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9330       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9331     }
9332
9333     /* rewind to the start of the game */
9334     currentMove = backwardMostMove;
9335
9336     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9337
9338     if (oldGameMode == AnalyzeFile ||
9339         oldGameMode == AnalyzeMode) {
9340       AnalyzeFileEvent();
9341     }
9342
9343     if (matchMode || appData.timeDelay == 0) {
9344       ToEndEvent();
9345       gameMode = EditGame;
9346       ModeHighlight();
9347     } else if (appData.timeDelay > 0) {
9348       AutoPlayGameLoop();
9349     }
9350
9351     if (appData.debugMode)
9352         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9353
9354     loadFlag = 0; /* [HGM] true game starts */
9355     return TRUE;
9356 }
9357
9358 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9359 int
9360 ReloadPosition(offset)
9361      int offset;
9362 {
9363     int positionNumber = lastLoadPositionNumber + offset;
9364     if (lastLoadPositionFP == NULL) {
9365         DisplayError(_("No position has been loaded yet"), 0);
9366         return FALSE;
9367     }
9368     if (positionNumber <= 0) {
9369         DisplayError(_("Can't back up any further"), 0);
9370         return FALSE;
9371     }
9372     return LoadPosition(lastLoadPositionFP, positionNumber,
9373                         lastLoadPositionTitle);
9374 }
9375
9376 /* Load the nth position from the given file */
9377 int
9378 LoadPositionFromFile(filename, n, title)
9379      char *filename;
9380      int n;
9381      char *title;
9382 {
9383     FILE *f;
9384     char buf[MSG_SIZ];
9385
9386     if (strcmp(filename, "-") == 0) {
9387         return LoadPosition(stdin, n, "stdin");
9388     } else {
9389         f = fopen(filename, "rb");
9390         if (f == NULL) {
9391             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9392             DisplayError(buf, errno);
9393             return FALSE;
9394         } else {
9395             return LoadPosition(f, n, title);
9396         }
9397     }
9398 }
9399
9400 /* Load the nth position from the given open file, and close it */
9401 int
9402 LoadPosition(f, positionNumber, title)
9403      FILE *f;
9404      int positionNumber;
9405      char *title;
9406 {
9407     char *p, line[MSG_SIZ];
9408     Board initial_position;
9409     int i, j, fenMode, pn;
9410
9411     if (gameMode == Training )
9412         SetTrainingModeOff();
9413
9414     if (gameMode != BeginningOfGame) {
9415         Reset(FALSE, TRUE);
9416     }
9417     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9418         fclose(lastLoadPositionFP);
9419     }
9420     if (positionNumber == 0) positionNumber = 1;
9421     lastLoadPositionFP = f;
9422     lastLoadPositionNumber = positionNumber;
9423     strcpy(lastLoadPositionTitle, title);
9424     if (first.pr == NoProc) {
9425       StartChessProgram(&first);
9426       InitChessProgram(&first, FALSE);
9427     }
9428     pn = positionNumber;
9429     if (positionNumber < 0) {
9430         /* Negative position number means to seek to that byte offset */
9431         if (fseek(f, -positionNumber, 0) == -1) {
9432             DisplayError(_("Can't seek on position file"), 0);
9433             return FALSE;
9434         };
9435         pn = 1;
9436     } else {
9437         if (fseek(f, 0, 0) == -1) {
9438             if (f == lastLoadPositionFP ?
9439                 positionNumber == lastLoadPositionNumber + 1 :
9440                 positionNumber == 1) {
9441                 pn = 1;
9442             } else {
9443                 DisplayError(_("Can't seek on position file"), 0);
9444                 return FALSE;
9445             }
9446         }
9447     }
9448     /* See if this file is FEN or old-style xboard */
9449     if (fgets(line, MSG_SIZ, f) == NULL) {
9450         DisplayError(_("Position not found in file"), 0);
9451         return FALSE;
9452     }
9453 #if 0
9454     switch (line[0]) {
9455       case '#':  case 'x':
9456       default:
9457         fenMode = FALSE;
9458         break;
9459       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9460       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9461       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9462       case '7':  case '8':  case '9':
9463       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9464       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9465       case 'C':  case 'W':             case 'c':  case 'w':
9466         fenMode = TRUE;
9467         break;
9468     }
9469 #else
9470     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9471     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9472 #endif
9473
9474     if (pn >= 2) {
9475         if (fenMode || line[0] == '#') pn--;
9476         while (pn > 0) {
9477             /* skip positions before number pn */
9478             if (fgets(line, MSG_SIZ, f) == NULL) {
9479                 Reset(TRUE, TRUE);
9480                 DisplayError(_("Position not found in file"), 0);
9481                 return FALSE;
9482             }
9483             if (fenMode || line[0] == '#') pn--;
9484         }
9485     }
9486
9487     if (fenMode) {
9488         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9489             DisplayError(_("Bad FEN position in file"), 0);
9490             return FALSE;
9491         }
9492     } else {
9493         (void) fgets(line, MSG_SIZ, f);
9494         (void) fgets(line, MSG_SIZ, f);
9495
9496         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9497             (void) fgets(line, MSG_SIZ, f);
9498             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9499                 if (*p == ' ')
9500                   continue;
9501                 initial_position[i][j++] = CharToPiece(*p);
9502             }
9503         }
9504
9505         blackPlaysFirst = FALSE;
9506         if (!feof(f)) {
9507             (void) fgets(line, MSG_SIZ, f);
9508             if (strncmp(line, "black", strlen("black"))==0)
9509               blackPlaysFirst = TRUE;
9510         }
9511     }
9512     startedFromSetupPosition = TRUE;
9513
9514     SendToProgram("force\n", &first);
9515     CopyBoard(boards[0], initial_position);
9516     if (blackPlaysFirst) {
9517         currentMove = forwardMostMove = backwardMostMove = 1;
9518         strcpy(moveList[0], "");
9519         strcpy(parseList[0], "");
9520         CopyBoard(boards[1], initial_position);
9521         DisplayMessage("", _("Black to play"));
9522     } else {
9523         currentMove = forwardMostMove = backwardMostMove = 0;
9524         DisplayMessage("", _("White to play"));
9525     }
9526           /* [HGM] copy FEN attributes as well */
9527           {   int i;
9528               initialRulePlies = FENrulePlies;
9529               epStatus[forwardMostMove] = FENepStatus;
9530               for( i=0; i< nrCastlingRights; i++ )
9531                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9532           }
9533     SendBoard(&first, forwardMostMove);
9534     if (appData.debugMode) {
9535 int i, j;
9536   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9537   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9538         fprintf(debugFP, "Load Position\n");
9539     }
9540
9541     if (positionNumber > 1) {
9542         sprintf(line, "%s %d", title, positionNumber);
9543         DisplayTitle(line);
9544     } else {
9545         DisplayTitle(title);
9546     }
9547     gameMode = EditGame;
9548     ModeHighlight();
9549     ResetClocks();
9550     timeRemaining[0][1] = whiteTimeRemaining;
9551     timeRemaining[1][1] = blackTimeRemaining;
9552     DrawPosition(FALSE, boards[currentMove]);
9553
9554     return TRUE;
9555 }
9556
9557
9558 void
9559 CopyPlayerNameIntoFileName(dest, src)
9560      char **dest, *src;
9561 {
9562     while (*src != NULLCHAR && *src != ',') {
9563         if (*src == ' ') {
9564             *(*dest)++ = '_';
9565             src++;
9566         } else {
9567             *(*dest)++ = *src++;
9568         }
9569     }
9570 }
9571
9572 char *DefaultFileName(ext)
9573      char *ext;
9574 {
9575     static char def[MSG_SIZ];
9576     char *p;
9577
9578     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9579         p = def;
9580         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9581         *p++ = '-';
9582         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9583         *p++ = '.';
9584         strcpy(p, ext);
9585     } else {
9586         def[0] = NULLCHAR;
9587     }
9588     return def;
9589 }
9590
9591 /* Save the current game to the given file */
9592 int
9593 SaveGameToFile(filename, append)
9594      char *filename;
9595      int append;
9596 {
9597     FILE *f;
9598     char buf[MSG_SIZ];
9599
9600     if (strcmp(filename, "-") == 0) {
9601         return SaveGame(stdout, 0, NULL);
9602     } else {
9603         f = fopen(filename, append ? "a" : "w");
9604         if (f == NULL) {
9605             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9606             DisplayError(buf, errno);
9607             return FALSE;
9608         } else {
9609             return SaveGame(f, 0, NULL);
9610         }
9611     }
9612 }
9613
9614 char *
9615 SavePart(str)
9616      char *str;
9617 {
9618     static char buf[MSG_SIZ];
9619     char *p;
9620
9621     p = strchr(str, ' ');
9622     if (p == NULL) return str;
9623     strncpy(buf, str, p - str);
9624     buf[p - str] = NULLCHAR;
9625     return buf;
9626 }
9627
9628 #define PGN_MAX_LINE 75
9629
9630 #define PGN_SIDE_WHITE  0
9631 #define PGN_SIDE_BLACK  1
9632
9633 /* [AS] */
9634 static int FindFirstMoveOutOfBook( int side )
9635 {
9636     int result = -1;
9637
9638     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9639         int index = backwardMostMove;
9640         int has_book_hit = 0;
9641
9642         if( (index % 2) != side ) {
9643             index++;
9644         }
9645
9646         while( index < forwardMostMove ) {
9647             /* Check to see if engine is in book */
9648             int depth = pvInfoList[index].depth;
9649             int score = pvInfoList[index].score;
9650             int in_book = 0;
9651
9652             if( depth <= 2 ) {
9653                 in_book = 1;
9654             }
9655             else if( score == 0 && depth == 63 ) {
9656                 in_book = 1; /* Zappa */
9657             }
9658             else if( score == 2 && depth == 99 ) {
9659                 in_book = 1; /* Abrok */
9660             }
9661
9662             has_book_hit += in_book;
9663
9664             if( ! in_book ) {
9665                 result = index;
9666
9667                 break;
9668             }
9669
9670             index += 2;
9671         }
9672     }
9673
9674     return result;
9675 }
9676
9677 /* [AS] */
9678 void GetOutOfBookInfo( char * buf )
9679 {
9680     int oob[2];
9681     int i;
9682     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9683
9684     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9685     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9686
9687     *buf = '\0';
9688
9689     if( oob[0] >= 0 || oob[1] >= 0 ) {
9690         for( i=0; i<2; i++ ) {
9691             int idx = oob[i];
9692
9693             if( idx >= 0 ) {
9694                 if( i > 0 && oob[0] >= 0 ) {
9695                     strcat( buf, "   " );
9696                 }
9697
9698                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9699                 sprintf( buf+strlen(buf), "%s%.2f",
9700                     pvInfoList[idx].score >= 0 ? "+" : "",
9701                     pvInfoList[idx].score / 100.0 );
9702             }
9703         }
9704     }
9705 }
9706
9707 /* Save game in PGN style and close the file */
9708 int
9709 SaveGamePGN(f)
9710      FILE *f;
9711 {
9712     int i, offset, linelen, newblock;
9713     time_t tm;
9714 //    char *movetext;
9715     char numtext[32];
9716     int movelen, numlen, blank;
9717     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9718
9719     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9720
9721     tm = time((time_t *) NULL);
9722
9723     PrintPGNTags(f, &gameInfo);
9724
9725     if (backwardMostMove > 0 || startedFromSetupPosition) {
9726         char *fen = PositionToFEN(backwardMostMove, NULL);
9727         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9728         fprintf(f, "\n{--------------\n");
9729         PrintPosition(f, backwardMostMove);
9730         fprintf(f, "--------------}\n");
9731         free(fen);
9732     }
9733     else {
9734         /* [AS] Out of book annotation */
9735         if( appData.saveOutOfBookInfo ) {
9736             char buf[64];
9737
9738             GetOutOfBookInfo( buf );
9739
9740             if( buf[0] != '\0' ) {
9741                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9742             }
9743         }
9744
9745         fprintf(f, "\n");
9746     }
9747
9748     i = backwardMostMove;
9749     linelen = 0;
9750     newblock = TRUE;
9751
9752     while (i < forwardMostMove) {
9753         /* Print comments preceding this move */
9754         if (commentList[i] != NULL) {
9755             if (linelen > 0) fprintf(f, "\n");
9756             fprintf(f, "{\n%s}\n", commentList[i]);
9757             linelen = 0;
9758             newblock = TRUE;
9759         }
9760
9761         /* Format move number */
9762         if ((i % 2) == 0) {
9763             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9764         } else {
9765             if (newblock) {
9766                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9767             } else {
9768                 numtext[0] = NULLCHAR;
9769             }
9770         }
9771         numlen = strlen(numtext);
9772         newblock = FALSE;
9773
9774         /* Print move number */
9775         blank = linelen > 0 && numlen > 0;
9776         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9777             fprintf(f, "\n");
9778             linelen = 0;
9779             blank = 0;
9780         }
9781         if (blank) {
9782             fprintf(f, " ");
9783             linelen++;
9784         }
9785         fprintf(f, numtext);
9786         linelen += numlen;
9787
9788         /* Get move */
9789         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9790         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9791 #if 0
9792         // SavePart already does this!
9793         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9794                 int p = movelen - 1;
9795                 if(move_buffer[p] == ' ') p--;
9796                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9797                     while(p && move_buffer[--p] != '(');
9798                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9799                 }
9800         }
9801 #endif
9802         /* Print move */
9803         blank = linelen > 0 && movelen > 0;
9804         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9805             fprintf(f, "\n");
9806             linelen = 0;
9807             blank = 0;
9808         }
9809         if (blank) {
9810             fprintf(f, " ");
9811             linelen++;
9812         }
9813         fprintf(f, move_buffer);
9814         linelen += movelen;
9815
9816         /* [AS] Add PV info if present */
9817         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9818             /* [HGM] add time */
9819             char buf[MSG_SIZ]; int seconds = 0;
9820
9821 #if 1
9822             if(i >= backwardMostMove) {
9823                 if(WhiteOnMove(i))
9824                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9825                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9826                 else
9827                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9828                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9829             }
9830             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9831 #else
9832             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9833 #endif
9834
9835             if( seconds <= 0) buf[0] = 0; else
9836             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9837                 seconds = (seconds + 4)/10; // round to full seconds
9838                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9839                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9840             }
9841
9842             sprintf( move_buffer, "{%s%.2f/%d%s}",
9843                 pvInfoList[i].score >= 0 ? "+" : "",
9844                 pvInfoList[i].score / 100.0,
9845                 pvInfoList[i].depth,
9846                 buf );
9847
9848             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9849
9850             /* Print score/depth */
9851             blank = linelen > 0 && movelen > 0;
9852             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9853                 fprintf(f, "\n");
9854                 linelen = 0;
9855                 blank = 0;
9856             }
9857             if (blank) {
9858                 fprintf(f, " ");
9859                 linelen++;
9860             }
9861             fprintf(f, move_buffer);
9862             linelen += movelen;
9863         }
9864
9865         i++;
9866     }
9867
9868     /* Start a new line */
9869     if (linelen > 0) fprintf(f, "\n");
9870
9871     /* Print comments after last move */
9872     if (commentList[i] != NULL) {
9873         fprintf(f, "{\n%s}\n", commentList[i]);
9874     }
9875
9876     /* Print result */
9877     if (gameInfo.resultDetails != NULL &&
9878         gameInfo.resultDetails[0] != NULLCHAR) {
9879         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9880                 PGNResult(gameInfo.result));
9881     } else {
9882         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9883     }
9884
9885     fclose(f);
9886     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9887     return TRUE;
9888 }
9889
9890 /* Save game in old style and close the file */
9891 int
9892 SaveGameOldStyle(f)
9893      FILE *f;
9894 {
9895     int i, offset;
9896     time_t tm;
9897
9898     tm = time((time_t *) NULL);
9899
9900     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9901     PrintOpponents(f);
9902
9903     if (backwardMostMove > 0 || startedFromSetupPosition) {
9904         fprintf(f, "\n[--------------\n");
9905         PrintPosition(f, backwardMostMove);
9906         fprintf(f, "--------------]\n");
9907     } else {
9908         fprintf(f, "\n");
9909     }
9910
9911     i = backwardMostMove;
9912     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9913
9914     while (i < forwardMostMove) {
9915         if (commentList[i] != NULL) {
9916             fprintf(f, "[%s]\n", commentList[i]);
9917         }
9918
9919         if ((i % 2) == 1) {
9920             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9921             i++;
9922         } else {
9923             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9924             i++;
9925             if (commentList[i] != NULL) {
9926                 fprintf(f, "\n");
9927                 continue;
9928             }
9929             if (i >= forwardMostMove) {
9930                 fprintf(f, "\n");
9931                 break;
9932             }
9933             fprintf(f, "%s\n", parseList[i]);
9934             i++;
9935         }
9936     }
9937
9938     if (commentList[i] != NULL) {
9939         fprintf(f, "[%s]\n", commentList[i]);
9940     }
9941
9942     /* This isn't really the old style, but it's close enough */
9943     if (gameInfo.resultDetails != NULL &&
9944         gameInfo.resultDetails[0] != NULLCHAR) {
9945         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9946                 gameInfo.resultDetails);
9947     } else {
9948         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9949     }
9950
9951     fclose(f);
9952     return TRUE;
9953 }
9954
9955 /* Save the current game to open file f and close the file */
9956 int
9957 SaveGame(f, dummy, dummy2)
9958      FILE *f;
9959      int dummy;
9960      char *dummy2;
9961 {
9962     if (gameMode == EditPosition) EditPositionDone();
9963     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9964     if (appData.oldSaveStyle)
9965       return SaveGameOldStyle(f);
9966     else
9967       return SaveGamePGN(f);
9968 }
9969
9970 /* Save the current position to the given file */
9971 int
9972 SavePositionToFile(filename)
9973      char *filename;
9974 {
9975     FILE *f;
9976     char buf[MSG_SIZ];
9977
9978     if (strcmp(filename, "-") == 0) {
9979         return SavePosition(stdout, 0, NULL);
9980     } else {
9981         f = fopen(filename, "a");
9982         if (f == NULL) {
9983             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9984             DisplayError(buf, errno);
9985             return FALSE;
9986         } else {
9987             SavePosition(f, 0, NULL);
9988             return TRUE;
9989         }
9990     }
9991 }
9992
9993 /* Save the current position to the given open file and close the file */
9994 int
9995 SavePosition(f, dummy, dummy2)
9996      FILE *f;
9997      int dummy;
9998      char *dummy2;
9999 {
10000     time_t tm;
10001     char *fen;
10002
10003     if (appData.oldSaveStyle) {
10004         tm = time((time_t *) NULL);
10005
10006         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10007         PrintOpponents(f);
10008         fprintf(f, "[--------------\n");
10009         PrintPosition(f, currentMove);
10010         fprintf(f, "--------------]\n");
10011     } else {
10012         fen = PositionToFEN(currentMove, NULL);
10013         fprintf(f, "%s\n", fen);
10014         free(fen);
10015     }
10016     fclose(f);
10017     return TRUE;
10018 }
10019
10020 void
10021 ReloadCmailMsgEvent(unregister)
10022      int unregister;
10023 {
10024 #if !WIN32
10025     static char *inFilename = NULL;
10026     static char *outFilename;
10027     int i;
10028     struct stat inbuf, outbuf;
10029     int status;
10030
10031     /* Any registered moves are unregistered if unregister is set, */
10032     /* i.e. invoked by the signal handler */
10033     if (unregister) {
10034         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10035             cmailMoveRegistered[i] = FALSE;
10036             if (cmailCommentList[i] != NULL) {
10037                 free(cmailCommentList[i]);
10038                 cmailCommentList[i] = NULL;
10039             }
10040         }
10041         nCmailMovesRegistered = 0;
10042     }
10043
10044     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10045         cmailResult[i] = CMAIL_NOT_RESULT;
10046     }
10047     nCmailResults = 0;
10048
10049     if (inFilename == NULL) {
10050         /* Because the filenames are static they only get malloced once  */
10051         /* and they never get freed                                      */
10052         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10053         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10054
10055         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10056         sprintf(outFilename, "%s.out", appData.cmailGameName);
10057     }
10058
10059     status = stat(outFilename, &outbuf);
10060     if (status < 0) {
10061         cmailMailedMove = FALSE;
10062     } else {
10063         status = stat(inFilename, &inbuf);
10064         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10065     }
10066
10067     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10068        counts the games, notes how each one terminated, etc.
10069
10070        It would be nice to remove this kludge and instead gather all
10071        the information while building the game list.  (And to keep it
10072        in the game list nodes instead of having a bunch of fixed-size
10073        parallel arrays.)  Note this will require getting each game's
10074        termination from the PGN tags, as the game list builder does
10075        not process the game moves.  --mann
10076        */
10077     cmailMsgLoaded = TRUE;
10078     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10079
10080     /* Load first game in the file or popup game menu */
10081     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10082
10083 #endif /* !WIN32 */
10084     return;
10085 }
10086
10087 int
10088 RegisterMove()
10089 {
10090     FILE *f;
10091     char string[MSG_SIZ];
10092
10093     if (   cmailMailedMove
10094         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10095         return TRUE;            /* Allow free viewing  */
10096     }
10097
10098     /* Unregister move to ensure that we don't leave RegisterMove        */
10099     /* with the move registered when the conditions for registering no   */
10100     /* longer hold                                                       */
10101     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10102         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10103         nCmailMovesRegistered --;
10104
10105         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10106           {
10107               free(cmailCommentList[lastLoadGameNumber - 1]);
10108               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10109           }
10110     }
10111
10112     if (cmailOldMove == -1) {
10113         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10114         return FALSE;
10115     }
10116
10117     if (currentMove > cmailOldMove + 1) {
10118         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10119         return FALSE;
10120     }
10121
10122     if (currentMove < cmailOldMove) {
10123         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10124         return FALSE;
10125     }
10126
10127     if (forwardMostMove > currentMove) {
10128         /* Silently truncate extra moves */
10129         TruncateGame();
10130     }
10131
10132     if (   (currentMove == cmailOldMove + 1)
10133         || (   (currentMove == cmailOldMove)
10134             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10135                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10136         if (gameInfo.result != GameUnfinished) {
10137             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10138         }
10139
10140         if (commentList[currentMove] != NULL) {
10141             cmailCommentList[lastLoadGameNumber - 1]
10142               = StrSave(commentList[currentMove]);
10143         }
10144         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10145
10146         if (appData.debugMode)
10147           fprintf(debugFP, "Saving %s for game %d\n",
10148                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10149
10150         sprintf(string,
10151                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10152
10153         f = fopen(string, "w");
10154         if (appData.oldSaveStyle) {
10155             SaveGameOldStyle(f); /* also closes the file */
10156
10157             sprintf(string, "%s.pos.out", appData.cmailGameName);
10158             f = fopen(string, "w");
10159             SavePosition(f, 0, NULL); /* also closes the file */
10160         } else {
10161             fprintf(f, "{--------------\n");
10162             PrintPosition(f, currentMove);
10163             fprintf(f, "--------------}\n\n");
10164
10165             SaveGame(f, 0, NULL); /* also closes the file*/
10166         }
10167
10168         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10169         nCmailMovesRegistered ++;
10170     } else if (nCmailGames == 1) {
10171         DisplayError(_("You have not made a move yet"), 0);
10172         return FALSE;
10173     }
10174
10175     return TRUE;
10176 }
10177
10178 void
10179 MailMoveEvent()
10180 {
10181 #if !WIN32
10182     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10183     FILE *commandOutput;
10184     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10185     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10186     int nBuffers;
10187     int i;
10188     int archived;
10189     char *arcDir;
10190
10191     if (! cmailMsgLoaded) {
10192         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10193         return;
10194     }
10195
10196     if (nCmailGames == nCmailResults) {
10197         DisplayError(_("No unfinished games"), 0);
10198         return;
10199     }
10200
10201 #if CMAIL_PROHIBIT_REMAIL
10202     if (cmailMailedMove) {
10203         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);
10204         DisplayError(msg, 0);
10205         return;
10206     }
10207 #endif
10208
10209     if (! (cmailMailedMove || RegisterMove())) return;
10210
10211     if (   cmailMailedMove
10212         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10213         sprintf(string, partCommandString,
10214                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10215         commandOutput = popen(string, "r");
10216
10217         if (commandOutput == NULL) {
10218             DisplayError(_("Failed to invoke cmail"), 0);
10219         } else {
10220             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10221                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10222             }
10223             if (nBuffers > 1) {
10224                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10225                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10226                 nBytes = MSG_SIZ - 1;
10227             } else {
10228                 (void) memcpy(msg, buffer, nBytes);
10229             }
10230             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10231
10232             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10233                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10234
10235                 archived = TRUE;
10236                 for (i = 0; i < nCmailGames; i ++) {
10237                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10238                         archived = FALSE;
10239                     }
10240                 }
10241                 if (   archived
10242                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10243                         != NULL)) {
10244                     sprintf(buffer, "%s/%s.%s.archive",
10245                             arcDir,
10246                             appData.cmailGameName,
10247                             gameInfo.date);
10248                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10249                     cmailMsgLoaded = FALSE;
10250                 }
10251             }
10252
10253             DisplayInformation(msg);
10254             pclose(commandOutput);
10255         }
10256     } else {
10257         if ((*cmailMsg) != '\0') {
10258             DisplayInformation(cmailMsg);
10259         }
10260     }
10261
10262     return;
10263 #endif /* !WIN32 */
10264 }
10265
10266 char *
10267 CmailMsg()
10268 {
10269 #if WIN32
10270     return NULL;
10271 #else
10272     int  prependComma = 0;
10273     char number[5];
10274     char string[MSG_SIZ];       /* Space for game-list */
10275     int  i;
10276
10277     if (!cmailMsgLoaded) return "";
10278
10279     if (cmailMailedMove) {
10280         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10281     } else {
10282         /* Create a list of games left */
10283         sprintf(string, "[");
10284         for (i = 0; i < nCmailGames; i ++) {
10285             if (! (   cmailMoveRegistered[i]
10286                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10287                 if (prependComma) {
10288                     sprintf(number, ",%d", i + 1);
10289                 } else {
10290                     sprintf(number, "%d", i + 1);
10291                     prependComma = 1;
10292                 }
10293
10294                 strcat(string, number);
10295             }
10296         }
10297         strcat(string, "]");
10298
10299         if (nCmailMovesRegistered + nCmailResults == 0) {
10300             switch (nCmailGames) {
10301               case 1:
10302                 sprintf(cmailMsg,
10303                         _("Still need to make move for game\n"));
10304                 break;
10305
10306               case 2:
10307                 sprintf(cmailMsg,
10308                         _("Still need to make moves for both games\n"));
10309                 break;
10310
10311               default:
10312                 sprintf(cmailMsg,
10313                         _("Still need to make moves for all %d games\n"),
10314                         nCmailGames);
10315                 break;
10316             }
10317         } else {
10318             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10319               case 1:
10320                 sprintf(cmailMsg,
10321                         _("Still need to make a move for game %s\n"),
10322                         string);
10323                 break;
10324
10325               case 0:
10326                 if (nCmailResults == nCmailGames) {
10327                     sprintf(cmailMsg, _("No unfinished games\n"));
10328                 } else {
10329                     sprintf(cmailMsg, _("Ready to send mail\n"));
10330                 }
10331                 break;
10332
10333               default:
10334                 sprintf(cmailMsg,
10335                         _("Still need to make moves for games %s\n"),
10336                         string);
10337             }
10338         }
10339     }
10340     return cmailMsg;
10341 #endif /* WIN32 */
10342 }
10343
10344 void
10345 ResetGameEvent()
10346 {
10347     if (gameMode == Training)
10348       SetTrainingModeOff();
10349
10350     Reset(TRUE, TRUE);
10351     cmailMsgLoaded = FALSE;
10352     if (appData.icsActive) {
10353       SendToICS(ics_prefix);
10354       SendToICS("refresh\n");
10355     }
10356 }
10357
10358 void
10359 ExitEvent(status)
10360      int status;
10361 {
10362     exiting++;
10363     if (exiting > 2) {
10364       /* Give up on clean exit */
10365       exit(status);
10366     }
10367     if (exiting > 1) {
10368       /* Keep trying for clean exit */
10369       return;
10370     }
10371
10372     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10373
10374     if (telnetISR != NULL) {
10375       RemoveInputSource(telnetISR);
10376     }
10377     if (icsPR != NoProc) {
10378       DestroyChildProcess(icsPR, TRUE);
10379     }
10380 #if 0
10381     /* Save game if resource set and not already saved by GameEnds() */
10382     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10383                              && forwardMostMove > 0) {
10384       if (*appData.saveGameFile != NULLCHAR) {
10385         SaveGameToFile(appData.saveGameFile, TRUE);
10386       } else if (appData.autoSaveGames) {
10387         AutoSaveGame();
10388       }
10389       if (*appData.savePositionFile != NULLCHAR) {
10390         SavePositionToFile(appData.savePositionFile);
10391       }
10392     }
10393     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10394 #else
10395     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10396     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10397 #endif
10398     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10399     /* make sure this other one finishes before killing it!                  */
10400     if(endingGame) { int count = 0;
10401         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10402         while(endingGame && count++ < 10) DoSleep(1);
10403         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10404     }
10405
10406     /* Kill off chess programs */
10407     if (first.pr != NoProc) {
10408         ExitAnalyzeMode();
10409
10410         DoSleep( appData.delayBeforeQuit );
10411         SendToProgram("quit\n", &first);
10412         DoSleep( appData.delayAfterQuit );
10413         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10414     }
10415     if (second.pr != NoProc) {
10416         DoSleep( appData.delayBeforeQuit );
10417         SendToProgram("quit\n", &second);
10418         DoSleep( appData.delayAfterQuit );
10419         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10420     }
10421     if (first.isr != NULL) {
10422         RemoveInputSource(first.isr);
10423     }
10424     if (second.isr != NULL) {
10425         RemoveInputSource(second.isr);
10426     }
10427
10428     ShutDownFrontEnd();
10429     exit(status);
10430 }
10431
10432 void
10433 PauseEvent()
10434 {
10435     if (appData.debugMode)
10436         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10437     if (pausing) {
10438         pausing = FALSE;
10439         ModeHighlight();
10440         if (gameMode == MachinePlaysWhite ||
10441             gameMode == MachinePlaysBlack) {
10442             StartClocks();
10443         } else {
10444             DisplayBothClocks();
10445         }
10446         if (gameMode == PlayFromGameFile) {
10447             if (appData.timeDelay >= 0)
10448                 AutoPlayGameLoop();
10449         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10450             Reset(FALSE, TRUE);
10451             SendToICS(ics_prefix);
10452             SendToICS("refresh\n");
10453         } else if (currentMove < forwardMostMove) {
10454             ForwardInner(forwardMostMove);
10455         }
10456         pauseExamInvalid = FALSE;
10457     } else {
10458         switch (gameMode) {
10459           default:
10460             return;
10461           case IcsExamining:
10462             pauseExamForwardMostMove = forwardMostMove;
10463             pauseExamInvalid = FALSE;
10464             /* fall through */
10465           case IcsObserving:
10466           case IcsPlayingWhite:
10467           case IcsPlayingBlack:
10468             pausing = TRUE;
10469             ModeHighlight();
10470             return;
10471           case PlayFromGameFile:
10472             (void) StopLoadGameTimer();
10473             pausing = TRUE;
10474             ModeHighlight();
10475             break;
10476           case BeginningOfGame:
10477             if (appData.icsActive) return;
10478             /* else fall through */
10479           case MachinePlaysWhite:
10480           case MachinePlaysBlack:
10481           case TwoMachinesPlay:
10482             if (forwardMostMove == 0)
10483               return;           /* don't pause if no one has moved */
10484             if ((gameMode == MachinePlaysWhite &&
10485                  !WhiteOnMove(forwardMostMove)) ||
10486                 (gameMode == MachinePlaysBlack &&
10487                  WhiteOnMove(forwardMostMove))) {
10488                 StopClocks();
10489             }
10490             pausing = TRUE;
10491             ModeHighlight();
10492             break;
10493         }
10494     }
10495 }
10496
10497 void
10498 EditCommentEvent()
10499 {
10500     char title[MSG_SIZ];
10501
10502     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10503         strcpy(title, _("Edit comment"));
10504     } else {
10505         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10506                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10507                 parseList[currentMove - 1]);
10508     }
10509
10510     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10511 }
10512
10513
10514 void
10515 EditTagsEvent()
10516 {
10517     char *tags = PGNTags(&gameInfo);
10518     EditTagsPopUp(tags);
10519     free(tags);
10520 }
10521
10522 void
10523 AnalyzeModeEvent()
10524 {
10525     if (appData.noChessProgram || gameMode == AnalyzeMode)
10526       return;
10527
10528     if (gameMode != AnalyzeFile) {
10529         if (!appData.icsEngineAnalyze) {
10530                EditGameEvent();
10531                if (gameMode != EditGame) return;
10532         }
10533         ResurrectChessProgram();
10534         SendToProgram("analyze\n", &first);
10535         first.analyzing = TRUE;
10536         /*first.maybeThinking = TRUE;*/
10537         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10538         AnalysisPopUp(_("Analysis"),
10539                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10540     }
10541     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10542     pausing = FALSE;
10543     ModeHighlight();
10544     SetGameInfo();
10545
10546     StartAnalysisClock();
10547     GetTimeMark(&lastNodeCountTime);
10548     lastNodeCount = 0;
10549 }
10550
10551 void
10552 AnalyzeFileEvent()
10553 {
10554     if (appData.noChessProgram || gameMode == AnalyzeFile)
10555       return;
10556
10557     if (gameMode != AnalyzeMode) {
10558         EditGameEvent();
10559         if (gameMode != EditGame) return;
10560         ResurrectChessProgram();
10561         SendToProgram("analyze\n", &first);
10562         first.analyzing = TRUE;
10563         /*first.maybeThinking = TRUE;*/
10564         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10565         AnalysisPopUp(_("Analysis"),
10566                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10567     }
10568     gameMode = AnalyzeFile;
10569     pausing = FALSE;
10570     ModeHighlight();
10571     SetGameInfo();
10572
10573     StartAnalysisClock();
10574     GetTimeMark(&lastNodeCountTime);
10575     lastNodeCount = 0;
10576 }
10577
10578 void
10579 MachineWhiteEvent()
10580 {
10581     char buf[MSG_SIZ];
10582     char *bookHit = NULL;
10583
10584     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10585       return;
10586
10587
10588     if (gameMode == PlayFromGameFile ||
10589         gameMode == TwoMachinesPlay  ||
10590         gameMode == Training         ||
10591         gameMode == AnalyzeMode      ||
10592         gameMode == EndOfGame)
10593         EditGameEvent();
10594
10595     if (gameMode == EditPosition)
10596         EditPositionDone();
10597
10598     if (!WhiteOnMove(currentMove)) {
10599         DisplayError(_("It is not White's turn"), 0);
10600         return;
10601     }
10602
10603     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10604       ExitAnalyzeMode();
10605
10606     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10607         gameMode == AnalyzeFile)
10608         TruncateGame();
10609
10610     ResurrectChessProgram();    /* in case it isn't running */
10611     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10612         gameMode = MachinePlaysWhite;
10613         ResetClocks();
10614     } else
10615     gameMode = MachinePlaysWhite;
10616     pausing = FALSE;
10617     ModeHighlight();
10618     SetGameInfo();
10619     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10620     DisplayTitle(buf);
10621     if (first.sendName) {
10622       sprintf(buf, "name %s\n", gameInfo.black);
10623       SendToProgram(buf, &first);
10624     }
10625     if (first.sendTime) {
10626       if (first.useColors) {
10627         SendToProgram("black\n", &first); /*gnu kludge*/
10628       }
10629       SendTimeRemaining(&first, TRUE);
10630     }
10631     if (first.useColors) {
10632       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10633     }
10634     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10635     SetMachineThinkingEnables();
10636     first.maybeThinking = TRUE;
10637     StartClocks();
10638     firstMove = FALSE;
10639
10640     if (appData.autoFlipView && !flipView) {
10641       flipView = !flipView;
10642       DrawPosition(FALSE, NULL);
10643       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10644     }
10645
10646     if(bookHit) { // [HGM] book: simulate book reply
10647         static char bookMove[MSG_SIZ]; // a bit generous?
10648
10649         programStats.nodes = programStats.depth = programStats.time =
10650         programStats.score = programStats.got_only_move = 0;
10651         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10652
10653         strcpy(bookMove, "move ");
10654         strcat(bookMove, bookHit);
10655         HandleMachineMove(bookMove, &first);
10656     }
10657 }
10658
10659 void
10660 MachineBlackEvent()
10661 {
10662     char buf[MSG_SIZ];
10663    char *bookHit = NULL;
10664
10665     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10666         return;
10667
10668
10669     if (gameMode == PlayFromGameFile ||
10670         gameMode == TwoMachinesPlay  ||
10671         gameMode == Training         ||
10672         gameMode == AnalyzeMode      ||
10673         gameMode == EndOfGame)
10674         EditGameEvent();
10675
10676     if (gameMode == EditPosition)
10677         EditPositionDone();
10678
10679     if (WhiteOnMove(currentMove)) {
10680         DisplayError(_("It is not Black's turn"), 0);
10681         return;
10682     }
10683
10684     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10685       ExitAnalyzeMode();
10686
10687     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10688         gameMode == AnalyzeFile)
10689         TruncateGame();
10690
10691     ResurrectChessProgram();    /* in case it isn't running */
10692     gameMode = MachinePlaysBlack;
10693     pausing = FALSE;
10694     ModeHighlight();
10695     SetGameInfo();
10696     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10697     DisplayTitle(buf);
10698     if (first.sendName) {
10699       sprintf(buf, "name %s\n", gameInfo.white);
10700       SendToProgram(buf, &first);
10701     }
10702     if (first.sendTime) {
10703       if (first.useColors) {
10704         SendToProgram("white\n", &first); /*gnu kludge*/
10705       }
10706       SendTimeRemaining(&first, FALSE);
10707     }
10708     if (first.useColors) {
10709       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10710     }
10711     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10712     SetMachineThinkingEnables();
10713     first.maybeThinking = TRUE;
10714     StartClocks();
10715
10716     if (appData.autoFlipView && flipView) {
10717       flipView = !flipView;
10718       DrawPosition(FALSE, NULL);
10719       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10720     }
10721     if(bookHit) { // [HGM] book: simulate book reply
10722         static char bookMove[MSG_SIZ]; // a bit generous?
10723
10724         programStats.nodes = programStats.depth = programStats.time =
10725         programStats.score = programStats.got_only_move = 0;
10726         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10727
10728         strcpy(bookMove, "move ");
10729         strcat(bookMove, bookHit);
10730         HandleMachineMove(bookMove, &first);
10731     }
10732 }
10733
10734
10735 void
10736 DisplayTwoMachinesTitle()
10737 {
10738     char buf[MSG_SIZ];
10739     if (appData.matchGames > 0) {
10740         if (first.twoMachinesColor[0] == 'w') {
10741             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10742                     gameInfo.white, gameInfo.black,
10743                     first.matchWins, second.matchWins,
10744                     matchGame - 1 - (first.matchWins + second.matchWins));
10745         } else {
10746             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10747                     gameInfo.white, gameInfo.black,
10748                     second.matchWins, first.matchWins,
10749                     matchGame - 1 - (first.matchWins + second.matchWins));
10750         }
10751     } else {
10752         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10753     }
10754     DisplayTitle(buf);
10755 }
10756
10757 void
10758 TwoMachinesEvent P((void))
10759 {
10760     int i;
10761     char buf[MSG_SIZ];
10762     ChessProgramState *onmove;
10763     char *bookHit = NULL;
10764
10765     if (appData.noChessProgram) return;
10766
10767     switch (gameMode) {
10768       case TwoMachinesPlay:
10769         return;
10770       case MachinePlaysWhite:
10771       case MachinePlaysBlack:
10772         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10773             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10774             return;
10775         }
10776         /* fall through */
10777       case BeginningOfGame:
10778       case PlayFromGameFile:
10779       case EndOfGame:
10780         EditGameEvent();
10781         if (gameMode != EditGame) return;
10782         break;
10783       case EditPosition:
10784         EditPositionDone();
10785         break;
10786       case AnalyzeMode:
10787       case AnalyzeFile:
10788         ExitAnalyzeMode();
10789         break;
10790       case EditGame:
10791       default:
10792         break;
10793     }
10794
10795     forwardMostMove = currentMove;
10796     ResurrectChessProgram();    /* in case first program isn't running */
10797
10798     if (second.pr == NULL) {
10799         StartChessProgram(&second);
10800         if (second.protocolVersion == 1) {
10801           TwoMachinesEventIfReady();
10802         } else {
10803           /* kludge: allow timeout for initial "feature" command */
10804           FreezeUI();
10805           DisplayMessage("", _("Starting second chess program"));
10806           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10807         }
10808         return;
10809     }
10810     DisplayMessage("", "");
10811     InitChessProgram(&second, FALSE);
10812     SendToProgram("force\n", &second);
10813     if (startedFromSetupPosition) {
10814         SendBoard(&second, backwardMostMove);
10815     if (appData.debugMode) {
10816         fprintf(debugFP, "Two Machines\n");
10817     }
10818     }
10819     for (i = backwardMostMove; i < forwardMostMove; i++) {
10820         SendMoveToProgram(i, &second);
10821     }
10822
10823     gameMode = TwoMachinesPlay;
10824     pausing = FALSE;
10825     ModeHighlight();
10826     SetGameInfo();
10827     DisplayTwoMachinesTitle();
10828     firstMove = TRUE;
10829     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10830         onmove = &first;
10831     } else {
10832         onmove = &second;
10833     }
10834
10835     SendToProgram(first.computerString, &first);
10836     if (first.sendName) {
10837       sprintf(buf, "name %s\n", second.tidy);
10838       SendToProgram(buf, &first);
10839     }
10840     SendToProgram(second.computerString, &second);
10841     if (second.sendName) {
10842       sprintf(buf, "name %s\n", first.tidy);
10843       SendToProgram(buf, &second);
10844     }
10845
10846     ResetClocks();
10847     if (!first.sendTime || !second.sendTime) {
10848         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10849         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10850     }
10851     if (onmove->sendTime) {
10852       if (onmove->useColors) {
10853         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10854       }
10855       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10856     }
10857     if (onmove->useColors) {
10858       SendToProgram(onmove->twoMachinesColor, onmove);
10859     }
10860     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10861 //    SendToProgram("go\n", onmove);
10862     onmove->maybeThinking = TRUE;
10863     SetMachineThinkingEnables();
10864
10865     StartClocks();
10866
10867     if(bookHit) { // [HGM] book: simulate book reply
10868         static char bookMove[MSG_SIZ]; // a bit generous?
10869
10870         programStats.nodes = programStats.depth = programStats.time =
10871         programStats.score = programStats.got_only_move = 0;
10872         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10873
10874         strcpy(bookMove, "move ");
10875         strcat(bookMove, bookHit);
10876         HandleMachineMove(bookMove, &first);
10877     }
10878 }
10879
10880 void
10881 TrainingEvent()
10882 {
10883     if (gameMode == Training) {
10884       SetTrainingModeOff();
10885       gameMode = PlayFromGameFile;
10886       DisplayMessage("", _("Training mode off"));
10887     } else {
10888       gameMode = Training;
10889       animateTraining = appData.animate;
10890
10891       /* make sure we are not already at the end of the game */
10892       if (currentMove < forwardMostMove) {
10893         SetTrainingModeOn();
10894         DisplayMessage("", _("Training mode on"));
10895       } else {
10896         gameMode = PlayFromGameFile;
10897         DisplayError(_("Already at end of game"), 0);
10898       }
10899     }
10900     ModeHighlight();
10901 }
10902
10903 void
10904 IcsClientEvent()
10905 {
10906     if (!appData.icsActive) return;
10907     switch (gameMode) {
10908       case IcsPlayingWhite:
10909       case IcsPlayingBlack:
10910       case IcsObserving:
10911       case IcsIdle:
10912       case BeginningOfGame:
10913       case IcsExamining:
10914         return;
10915
10916       case EditGame:
10917         break;
10918
10919       case EditPosition:
10920         EditPositionDone();
10921         break;
10922
10923       case AnalyzeMode:
10924       case AnalyzeFile:
10925         ExitAnalyzeMode();
10926         break;
10927
10928       default:
10929         EditGameEvent();
10930         break;
10931     }
10932
10933     gameMode = IcsIdle;
10934     ModeHighlight();
10935     return;
10936 }
10937
10938
10939 void
10940 EditGameEvent()
10941 {
10942     int i;
10943
10944     switch (gameMode) {
10945       case Training:
10946         SetTrainingModeOff();
10947         break;
10948       case MachinePlaysWhite:
10949       case MachinePlaysBlack:
10950       case BeginningOfGame:
10951         SendToProgram("force\n", &first);
10952         SetUserThinkingEnables();
10953         break;
10954       case PlayFromGameFile:
10955         (void) StopLoadGameTimer();
10956         if (gameFileFP != NULL) {
10957             gameFileFP = NULL;
10958         }
10959         break;
10960       case EditPosition:
10961         EditPositionDone();
10962         break;
10963       case AnalyzeMode:
10964       case AnalyzeFile:
10965         ExitAnalyzeMode();
10966         SendToProgram("force\n", &first);
10967         break;
10968       case TwoMachinesPlay:
10969         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10970         ResurrectChessProgram();
10971         SetUserThinkingEnables();
10972         break;
10973       case EndOfGame:
10974         ResurrectChessProgram();
10975         break;
10976       case IcsPlayingBlack:
10977       case IcsPlayingWhite:
10978         DisplayError(_("Warning: You are still playing a game"), 0);
10979         break;
10980       case IcsObserving:
10981         DisplayError(_("Warning: You are still observing a game"), 0);
10982         break;
10983       case IcsExamining:
10984         DisplayError(_("Warning: You are still examining a game"), 0);
10985         break;
10986       case IcsIdle:
10987         break;
10988       case EditGame:
10989       default:
10990         return;
10991     }
10992
10993     pausing = FALSE;
10994     StopClocks();
10995     first.offeredDraw = second.offeredDraw = 0;
10996
10997     if (gameMode == PlayFromGameFile) {
10998         whiteTimeRemaining = timeRemaining[0][currentMove];
10999         blackTimeRemaining = timeRemaining[1][currentMove];
11000         DisplayTitle("");
11001     }
11002
11003     if (gameMode == MachinePlaysWhite ||
11004         gameMode == MachinePlaysBlack ||
11005         gameMode == TwoMachinesPlay ||
11006         gameMode == EndOfGame) {
11007         i = forwardMostMove;
11008         while (i > currentMove) {
11009             SendToProgram("undo\n", &first);
11010             i--;
11011         }
11012         whiteTimeRemaining = timeRemaining[0][currentMove];
11013         blackTimeRemaining = timeRemaining[1][currentMove];
11014         DisplayBothClocks();
11015         if (whiteFlag || blackFlag) {
11016             whiteFlag = blackFlag = 0;
11017         }
11018         DisplayTitle("");
11019     }
11020
11021     gameMode = EditGame;
11022     ModeHighlight();
11023     SetGameInfo();
11024 }
11025
11026
11027 void
11028 EditPositionEvent()
11029 {
11030     if (gameMode == EditPosition) {
11031         EditGameEvent();
11032         return;
11033     }
11034
11035     EditGameEvent();
11036     if (gameMode != EditGame) return;
11037
11038     gameMode = EditPosition;
11039     ModeHighlight();
11040     SetGameInfo();
11041     if (currentMove > 0)
11042       CopyBoard(boards[0], boards[currentMove]);
11043
11044     blackPlaysFirst = !WhiteOnMove(currentMove);
11045     ResetClocks();
11046     currentMove = forwardMostMove = backwardMostMove = 0;
11047     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11048     DisplayMove(-1);
11049 }
11050
11051 void
11052 ExitAnalyzeMode()
11053 {
11054     /* [DM] icsEngineAnalyze - possible call from other functions */
11055     if (appData.icsEngineAnalyze) {
11056         appData.icsEngineAnalyze = FALSE;
11057
11058         DisplayMessage("",_("Close ICS engine analyze..."));
11059     }
11060     if (first.analysisSupport && first.analyzing) {
11061       SendToProgram("exit\n", &first);
11062       first.analyzing = FALSE;
11063     }
11064     AnalysisPopDown();
11065     thinkOutput[0] = NULLCHAR;
11066 }
11067
11068 void
11069 EditPositionDone()
11070 {
11071     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11072
11073     startedFromSetupPosition = TRUE;
11074     InitChessProgram(&first, FALSE);
11075     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11076     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11077         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11078         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11079     } else castlingRights[0][2] = -1;
11080     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11081         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11082         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11083     } else castlingRights[0][5] = -1;
11084     SendToProgram("force\n", &first);
11085     if (blackPlaysFirst) {
11086         strcpy(moveList[0], "");
11087         strcpy(parseList[0], "");
11088         currentMove = forwardMostMove = backwardMostMove = 1;
11089         CopyBoard(boards[1], boards[0]);
11090         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11091         { int i;
11092           epStatus[1] = epStatus[0];
11093           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11094         }
11095     } else {
11096         currentMove = forwardMostMove = backwardMostMove = 0;
11097     }
11098     SendBoard(&first, forwardMostMove);
11099     if (appData.debugMode) {
11100         fprintf(debugFP, "EditPosDone\n");
11101     }
11102     DisplayTitle("");
11103     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11104     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11105     gameMode = EditGame;
11106     ModeHighlight();
11107     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11108     ClearHighlights(); /* [AS] */
11109 }
11110
11111 /* Pause for `ms' milliseconds */
11112 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11113 void
11114 TimeDelay(ms)
11115      long ms;
11116 {
11117     TimeMark m1, m2;
11118
11119     GetTimeMark(&m1);
11120     do {
11121         GetTimeMark(&m2);
11122     } while (SubtractTimeMarks(&m2, &m1) < ms);
11123 }
11124
11125 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11126 void
11127 SendMultiLineToICS(buf)
11128      char *buf;
11129 {
11130     char temp[MSG_SIZ+1], *p;
11131     int len;
11132
11133     len = strlen(buf);
11134     if (len > MSG_SIZ)
11135       len = MSG_SIZ;
11136
11137     strncpy(temp, buf, len);
11138     temp[len] = 0;
11139
11140     p = temp;
11141     while (*p) {
11142         if (*p == '\n' || *p == '\r')
11143           *p = ' ';
11144         ++p;
11145     }
11146
11147     strcat(temp, "\n");
11148     SendToICS(temp);
11149     SendToPlayer(temp, strlen(temp));
11150 }
11151
11152 void
11153 SetWhiteToPlayEvent()
11154 {
11155     if (gameMode == EditPosition) {
11156         blackPlaysFirst = FALSE;
11157         DisplayBothClocks();    /* works because currentMove is 0 */
11158     } else if (gameMode == IcsExamining) {
11159         SendToICS(ics_prefix);
11160         SendToICS("tomove white\n");
11161     }
11162 }
11163
11164 void
11165 SetBlackToPlayEvent()
11166 {
11167     if (gameMode == EditPosition) {
11168         blackPlaysFirst = TRUE;
11169         currentMove = 1;        /* kludge */
11170         DisplayBothClocks();
11171         currentMove = 0;
11172     } else if (gameMode == IcsExamining) {
11173         SendToICS(ics_prefix);
11174         SendToICS("tomove black\n");
11175     }
11176 }
11177
11178 void
11179 EditPositionMenuEvent(selection, x, y)
11180      ChessSquare selection;
11181      int x, y;
11182 {
11183     char buf[MSG_SIZ];
11184     ChessSquare piece = boards[0][y][x];
11185
11186     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11187
11188     switch (selection) {
11189       case ClearBoard:
11190         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11191             SendToICS(ics_prefix);
11192             SendToICS("bsetup clear\n");
11193         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11194             SendToICS(ics_prefix);
11195             SendToICS("clearboard\n");
11196         } else {
11197             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11198                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11199                 for (y = 0; y < BOARD_HEIGHT; y++) {
11200                     if (gameMode == IcsExamining) {
11201                         if (boards[currentMove][y][x] != EmptySquare) {
11202                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11203                                     AAA + x, ONE + y);
11204                             SendToICS(buf);
11205                         }
11206                     } else {
11207                         boards[0][y][x] = p;
11208                     }
11209                 }
11210             }
11211         }
11212         if (gameMode == EditPosition) {
11213             DrawPosition(FALSE, boards[0]);
11214         }
11215         break;
11216
11217       case WhitePlay:
11218         SetWhiteToPlayEvent();
11219         break;
11220
11221       case BlackPlay:
11222         SetBlackToPlayEvent();
11223         break;
11224
11225       case EmptySquare:
11226         if (gameMode == IcsExamining) {
11227             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11228             SendToICS(buf);
11229         } else {
11230             boards[0][y][x] = EmptySquare;
11231             DrawPosition(FALSE, boards[0]);
11232         }
11233         break;
11234
11235       case PromotePiece:
11236         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11237            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11238             selection = (ChessSquare) (PROMOTED piece);
11239         } else if(piece == EmptySquare) selection = WhiteSilver;
11240         else selection = (ChessSquare)((int)piece - 1);
11241         goto defaultlabel;
11242
11243       case DemotePiece:
11244         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11245            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11246             selection = (ChessSquare) (DEMOTED piece);
11247         } else if(piece == EmptySquare) selection = BlackSilver;
11248         else selection = (ChessSquare)((int)piece + 1);
11249         goto defaultlabel;
11250
11251       case WhiteQueen:
11252       case BlackQueen:
11253         if(gameInfo.variant == VariantShatranj ||
11254            gameInfo.variant == VariantXiangqi  ||
11255            gameInfo.variant == VariantCourier    )
11256             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11257         goto defaultlabel;
11258
11259       case WhiteKing:
11260       case BlackKing:
11261         if(gameInfo.variant == VariantXiangqi)
11262             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11263         if(gameInfo.variant == VariantKnightmate)
11264             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11265       default:
11266         defaultlabel:
11267         if (gameMode == IcsExamining) {
11268             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11269                     PieceToChar(selection), AAA + x, ONE + y);
11270             SendToICS(buf);
11271         } else {
11272             boards[0][y][x] = selection;
11273             DrawPosition(FALSE, boards[0]);
11274         }
11275         break;
11276     }
11277 }
11278
11279
11280 void
11281 DropMenuEvent(selection, x, y)
11282      ChessSquare selection;
11283      int x, y;
11284 {
11285     ChessMove moveType;
11286
11287     switch (gameMode) {
11288       case IcsPlayingWhite:
11289       case MachinePlaysBlack:
11290         if (!WhiteOnMove(currentMove)) {
11291             DisplayMoveError(_("It is Black's turn"));
11292             return;
11293         }
11294         moveType = WhiteDrop;
11295         break;
11296       case IcsPlayingBlack:
11297       case MachinePlaysWhite:
11298         if (WhiteOnMove(currentMove)) {
11299             DisplayMoveError(_("It is White's turn"));
11300             return;
11301         }
11302         moveType = BlackDrop;
11303         break;
11304       case EditGame:
11305         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11306         break;
11307       default:
11308         return;
11309     }
11310
11311     if (moveType == BlackDrop && selection < BlackPawn) {
11312       selection = (ChessSquare) ((int) selection
11313                                  + (int) BlackPawn - (int) WhitePawn);
11314     }
11315     if (boards[currentMove][y][x] != EmptySquare) {
11316         DisplayMoveError(_("That square is occupied"));
11317         return;
11318     }
11319
11320     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11321 }
11322
11323 void
11324 AcceptEvent()
11325 {
11326     /* Accept a pending offer of any kind from opponent */
11327
11328     if (appData.icsActive) {
11329         SendToICS(ics_prefix);
11330         SendToICS("accept\n");
11331     } else if (cmailMsgLoaded) {
11332         if (currentMove == cmailOldMove &&
11333             commentList[cmailOldMove] != NULL &&
11334             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11335                    "Black offers a draw" : "White offers a draw")) {
11336             TruncateGame();
11337             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11338             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11339         } else {
11340             DisplayError(_("There is no pending offer on this move"), 0);
11341             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11342         }
11343     } else {
11344         /* Not used for offers from chess program */
11345     }
11346 }
11347
11348 void
11349 DeclineEvent()
11350 {
11351     /* Decline a pending offer of any kind from opponent */
11352
11353     if (appData.icsActive) {
11354         SendToICS(ics_prefix);
11355         SendToICS("decline\n");
11356     } else if (cmailMsgLoaded) {
11357         if (currentMove == cmailOldMove &&
11358             commentList[cmailOldMove] != NULL &&
11359             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11360                    "Black offers a draw" : "White offers a draw")) {
11361 #ifdef NOTDEF
11362             AppendComment(cmailOldMove, "Draw declined");
11363             DisplayComment(cmailOldMove - 1, "Draw declined");
11364 #endif /*NOTDEF*/
11365         } else {
11366             DisplayError(_("There is no pending offer on this move"), 0);
11367         }
11368     } else {
11369         /* Not used for offers from chess program */
11370     }
11371 }
11372
11373 void
11374 RematchEvent()
11375 {
11376     /* Issue ICS rematch command */
11377     if (appData.icsActive) {
11378         SendToICS(ics_prefix);
11379         SendToICS("rematch\n");
11380     }
11381 }
11382
11383 void
11384 CallFlagEvent()
11385 {
11386     /* Call your opponent's flag (claim a win on time) */
11387     if (appData.icsActive) {
11388         SendToICS(ics_prefix);
11389         SendToICS("flag\n");
11390     } else {
11391         switch (gameMode) {
11392           default:
11393             return;
11394           case MachinePlaysWhite:
11395             if (whiteFlag) {
11396                 if (blackFlag)
11397                   GameEnds(GameIsDrawn, "Both players ran out of time",
11398                            GE_PLAYER);
11399                 else
11400                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11401             } else {
11402                 DisplayError(_("Your opponent is not out of time"), 0);
11403             }
11404             break;
11405           case MachinePlaysBlack:
11406             if (blackFlag) {
11407                 if (whiteFlag)
11408                   GameEnds(GameIsDrawn, "Both players ran out of time",
11409                            GE_PLAYER);
11410                 else
11411                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11412             } else {
11413                 DisplayError(_("Your opponent is not out of time"), 0);
11414             }
11415             break;
11416         }
11417     }
11418 }
11419
11420 void
11421 DrawEvent()
11422 {
11423     /* Offer draw or accept pending draw offer from opponent */
11424
11425     if (appData.icsActive) {
11426         /* Note: tournament rules require draw offers to be
11427            made after you make your move but before you punch
11428            your clock.  Currently ICS doesn't let you do that;
11429            instead, you immediately punch your clock after making
11430            a move, but you can offer a draw at any time. */
11431
11432         SendToICS(ics_prefix);
11433         SendToICS("draw\n");
11434     } else if (cmailMsgLoaded) {
11435         if (currentMove == cmailOldMove &&
11436             commentList[cmailOldMove] != NULL &&
11437             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11438                    "Black offers a draw" : "White offers a draw")) {
11439             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11440             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11441         } else if (currentMove == cmailOldMove + 1) {
11442             char *offer = WhiteOnMove(cmailOldMove) ?
11443               "White offers a draw" : "Black offers a draw";
11444             AppendComment(currentMove, offer);
11445             DisplayComment(currentMove - 1, offer);
11446             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11447         } else {
11448             DisplayError(_("You must make your move before offering a draw"), 0);
11449             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11450         }
11451     } else if (first.offeredDraw) {
11452         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11453     } else {
11454         if (first.sendDrawOffers) {
11455             SendToProgram("draw\n", &first);
11456             userOfferedDraw = TRUE;
11457         }
11458     }
11459 }
11460
11461 void
11462 AdjournEvent()
11463 {
11464     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11465
11466     if (appData.icsActive) {
11467         SendToICS(ics_prefix);
11468         SendToICS("adjourn\n");
11469     } else {
11470         /* Currently GNU Chess doesn't offer or accept Adjourns */
11471     }
11472 }
11473
11474
11475 void
11476 AbortEvent()
11477 {
11478     /* Offer Abort or accept pending Abort offer from opponent */
11479
11480     if (appData.icsActive) {
11481         SendToICS(ics_prefix);
11482         SendToICS("abort\n");
11483     } else {
11484         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11485     }
11486 }
11487
11488 void
11489 ResignEvent()
11490 {
11491     /* Resign.  You can do this even if it's not your turn. */
11492
11493     if (appData.icsActive) {
11494         SendToICS(ics_prefix);
11495         SendToICS("resign\n");
11496     } else {
11497         switch (gameMode) {
11498           case MachinePlaysWhite:
11499             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11500             break;
11501           case MachinePlaysBlack:
11502             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11503             break;
11504           case EditGame:
11505             if (cmailMsgLoaded) {
11506                 TruncateGame();
11507                 if (WhiteOnMove(cmailOldMove)) {
11508                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11509                 } else {
11510                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11511                 }
11512                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11513             }
11514             break;
11515           default:
11516             break;
11517         }
11518     }
11519 }
11520
11521
11522 void
11523 StopObservingEvent()
11524 {
11525     /* Stop observing current games */
11526     SendToICS(ics_prefix);
11527     SendToICS("unobserve\n");
11528 }
11529
11530 void
11531 StopExaminingEvent()
11532 {
11533     /* Stop observing current game */
11534     SendToICS(ics_prefix);
11535     SendToICS("unexamine\n");
11536 }
11537
11538 void
11539 ForwardInner(target)
11540      int target;
11541 {
11542     int limit;
11543
11544     if (appData.debugMode)
11545         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11546                 target, currentMove, forwardMostMove);
11547
11548     if (gameMode == EditPosition)
11549       return;
11550
11551     if (gameMode == PlayFromGameFile && !pausing)
11552       PauseEvent();
11553
11554     if (gameMode == IcsExamining && pausing)
11555       limit = pauseExamForwardMostMove;
11556     else
11557       limit = forwardMostMove;
11558
11559     if (target > limit) target = limit;
11560
11561     if (target > 0 && moveList[target - 1][0]) {
11562         int fromX, fromY, toX, toY;
11563         toX = moveList[target - 1][2] - AAA;
11564         toY = moveList[target - 1][3] - ONE;
11565         if (moveList[target - 1][1] == '@') {
11566             if (appData.highlightLastMove) {
11567                 SetHighlights(-1, -1, toX, toY);
11568             }
11569         } else {
11570             fromX = moveList[target - 1][0] - AAA;
11571             fromY = moveList[target - 1][1] - ONE;
11572             if (target == currentMove + 1) {
11573                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11574             }
11575             if (appData.highlightLastMove) {
11576                 SetHighlights(fromX, fromY, toX, toY);
11577             }
11578         }
11579     }
11580     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11581         gameMode == Training || gameMode == PlayFromGameFile ||
11582         gameMode == AnalyzeFile) {
11583         while (currentMove < target) {
11584             SendMoveToProgram(currentMove++, &first);
11585         }
11586     } else {
11587         currentMove = target;
11588     }
11589
11590     if (gameMode == EditGame || gameMode == EndOfGame) {
11591         whiteTimeRemaining = timeRemaining[0][currentMove];
11592         blackTimeRemaining = timeRemaining[1][currentMove];
11593     }
11594     DisplayBothClocks();
11595     DisplayMove(currentMove - 1);
11596     DrawPosition(FALSE, boards[currentMove]);
11597     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11598     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11599         DisplayComment(currentMove - 1, commentList[currentMove]);
11600     }
11601 }
11602
11603
11604 void
11605 ForwardEvent()
11606 {
11607     if (gameMode == IcsExamining && !pausing) {
11608         SendToICS(ics_prefix);
11609         SendToICS("forward\n");
11610     } else {
11611         ForwardInner(currentMove + 1);
11612     }
11613 }
11614
11615 void
11616 ToEndEvent()
11617 {
11618     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11619         /* to optimze, we temporarily turn off analysis mode while we feed
11620          * the remaining moves to the engine. Otherwise we get analysis output
11621          * after each move.
11622          */
11623         if (first.analysisSupport) {
11624           SendToProgram("exit\nforce\n", &first);
11625           first.analyzing = FALSE;
11626         }
11627     }
11628
11629     if (gameMode == IcsExamining && !pausing) {
11630         SendToICS(ics_prefix);
11631         SendToICS("forward 999999\n");
11632     } else {
11633         ForwardInner(forwardMostMove);
11634     }
11635
11636     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11637         /* we have fed all the moves, so reactivate analysis mode */
11638         SendToProgram("analyze\n", &first);
11639         first.analyzing = TRUE;
11640         /*first.maybeThinking = TRUE;*/
11641         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11642     }
11643 }
11644
11645 void
11646 BackwardInner(target)
11647      int target;
11648 {
11649     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11650
11651     if (appData.debugMode)
11652         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11653                 target, currentMove, forwardMostMove);
11654
11655     if (gameMode == EditPosition) return;
11656     if (currentMove <= backwardMostMove) {
11657         ClearHighlights();
11658         DrawPosition(full_redraw, boards[currentMove]);
11659         return;
11660     }
11661     if (gameMode == PlayFromGameFile && !pausing)
11662       PauseEvent();
11663
11664     if (moveList[target][0]) {
11665         int fromX, fromY, toX, toY;
11666         toX = moveList[target][2] - AAA;
11667         toY = moveList[target][3] - ONE;
11668         if (moveList[target][1] == '@') {
11669             if (appData.highlightLastMove) {
11670                 SetHighlights(-1, -1, toX, toY);
11671             }
11672         } else {
11673             fromX = moveList[target][0] - AAA;
11674             fromY = moveList[target][1] - ONE;
11675             if (target == currentMove - 1) {
11676                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11677             }
11678             if (appData.highlightLastMove) {
11679                 SetHighlights(fromX, fromY, toX, toY);
11680             }
11681         }
11682     }
11683     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11684         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11685         while (currentMove > target) {
11686             SendToProgram("undo\n", &first);
11687             currentMove--;
11688         }
11689     } else {
11690         currentMove = target;
11691     }
11692
11693     if (gameMode == EditGame || gameMode == EndOfGame) {
11694         whiteTimeRemaining = timeRemaining[0][currentMove];
11695         blackTimeRemaining = timeRemaining[1][currentMove];
11696     }
11697     DisplayBothClocks();
11698     DisplayMove(currentMove - 1);
11699     DrawPosition(full_redraw, boards[currentMove]);
11700     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11701     // [HGM] PV info: routine tests if comment empty
11702     DisplayComment(currentMove - 1, commentList[currentMove]);
11703 }
11704
11705 void
11706 BackwardEvent()
11707 {
11708     if (gameMode == IcsExamining && !pausing) {
11709         SendToICS(ics_prefix);
11710         SendToICS("backward\n");
11711     } else {
11712         BackwardInner(currentMove - 1);
11713     }
11714 }
11715
11716 void
11717 ToStartEvent()
11718 {
11719     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11720         /* to optimze, we temporarily turn off analysis mode while we undo
11721          * all the moves. Otherwise we get analysis output after each undo.
11722          */
11723         if (first.analysisSupport) {
11724           SendToProgram("exit\nforce\n", &first);
11725           first.analyzing = FALSE;
11726         }
11727     }
11728
11729     if (gameMode == IcsExamining && !pausing) {
11730         SendToICS(ics_prefix);
11731         SendToICS("backward 999999\n");
11732     } else {
11733         BackwardInner(backwardMostMove);
11734     }
11735
11736     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11737         /* we have fed all the moves, so reactivate analysis mode */
11738         SendToProgram("analyze\n", &first);
11739         first.analyzing = TRUE;
11740         /*first.maybeThinking = TRUE;*/
11741         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11742     }
11743 }
11744
11745 void
11746 ToNrEvent(int to)
11747 {
11748   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11749   if (to >= forwardMostMove) to = forwardMostMove;
11750   if (to <= backwardMostMove) to = backwardMostMove;
11751   if (to < currentMove) {
11752     BackwardInner(to);
11753   } else {
11754     ForwardInner(to);
11755   }
11756 }
11757
11758 void
11759 RevertEvent()
11760 {
11761     if (gameMode != IcsExamining) {
11762         DisplayError(_("You are not examining a game"), 0);
11763         return;
11764     }
11765     if (pausing) {
11766         DisplayError(_("You can't revert while pausing"), 0);
11767         return;
11768     }
11769     SendToICS(ics_prefix);
11770     SendToICS("revert\n");
11771 }
11772
11773 void
11774 RetractMoveEvent()
11775 {
11776     switch (gameMode) {
11777       case MachinePlaysWhite:
11778       case MachinePlaysBlack:
11779         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11780             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11781             return;
11782         }
11783         if (forwardMostMove < 2) return;
11784         currentMove = forwardMostMove = forwardMostMove - 2;
11785         whiteTimeRemaining = timeRemaining[0][currentMove];
11786         blackTimeRemaining = timeRemaining[1][currentMove];
11787         DisplayBothClocks();
11788         DisplayMove(currentMove - 1);
11789         ClearHighlights();/*!! could figure this out*/
11790         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11791         SendToProgram("remove\n", &first);
11792         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11793         break;
11794
11795       case BeginningOfGame:
11796       default:
11797         break;
11798
11799       case IcsPlayingWhite:
11800       case IcsPlayingBlack:
11801         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11802             SendToICS(ics_prefix);
11803             SendToICS("takeback 2\n");
11804         } else {
11805             SendToICS(ics_prefix);
11806             SendToICS("takeback 1\n");
11807         }
11808         break;
11809     }
11810 }
11811
11812 void
11813 MoveNowEvent()
11814 {
11815     ChessProgramState *cps;
11816
11817     switch (gameMode) {
11818       case MachinePlaysWhite:
11819         if (!WhiteOnMove(forwardMostMove)) {
11820             DisplayError(_("It is your turn"), 0);
11821             return;
11822         }
11823         cps = &first;
11824         break;
11825       case MachinePlaysBlack:
11826         if (WhiteOnMove(forwardMostMove)) {
11827             DisplayError(_("It is your turn"), 0);
11828             return;
11829         }
11830         cps = &first;
11831         break;
11832       case TwoMachinesPlay:
11833         if (WhiteOnMove(forwardMostMove) ==
11834             (first.twoMachinesColor[0] == 'w')) {
11835             cps = &first;
11836         } else {
11837             cps = &second;
11838         }
11839         break;
11840       case BeginningOfGame:
11841       default:
11842         return;
11843     }
11844     SendToProgram("?\n", cps);
11845 }
11846
11847 void
11848 TruncateGameEvent()
11849 {
11850     EditGameEvent();
11851     if (gameMode != EditGame) return;
11852     TruncateGame();
11853 }
11854
11855 void
11856 TruncateGame()
11857 {
11858     if (forwardMostMove > currentMove) {
11859         if (gameInfo.resultDetails != NULL) {
11860             free(gameInfo.resultDetails);
11861             gameInfo.resultDetails = NULL;
11862             gameInfo.result = GameUnfinished;
11863         }
11864         forwardMostMove = currentMove;
11865         HistorySet(parseList, backwardMostMove, forwardMostMove,
11866                    currentMove-1);
11867     }
11868 }
11869
11870 void
11871 HintEvent()
11872 {
11873     if (appData.noChessProgram) return;
11874     switch (gameMode) {
11875       case MachinePlaysWhite:
11876         if (WhiteOnMove(forwardMostMove)) {
11877             DisplayError(_("Wait until your turn"), 0);
11878             return;
11879         }
11880         break;
11881       case BeginningOfGame:
11882       case MachinePlaysBlack:
11883         if (!WhiteOnMove(forwardMostMove)) {
11884             DisplayError(_("Wait until your turn"), 0);
11885             return;
11886         }
11887         break;
11888       default:
11889         DisplayError(_("No hint available"), 0);
11890         return;
11891     }
11892     SendToProgram("hint\n", &first);
11893     hintRequested = TRUE;
11894 }
11895
11896 void
11897 BookEvent()
11898 {
11899     if (appData.noChessProgram) return;
11900     switch (gameMode) {
11901       case MachinePlaysWhite:
11902         if (WhiteOnMove(forwardMostMove)) {
11903             DisplayError(_("Wait until your turn"), 0);
11904             return;
11905         }
11906         break;
11907       case BeginningOfGame:
11908       case MachinePlaysBlack:
11909         if (!WhiteOnMove(forwardMostMove)) {
11910             DisplayError(_("Wait until your turn"), 0);
11911             return;
11912         }
11913         break;
11914       case EditPosition:
11915         EditPositionDone();
11916         break;
11917       case TwoMachinesPlay:
11918         return;
11919       default:
11920         break;
11921     }
11922     SendToProgram("bk\n", &first);
11923     bookOutput[0] = NULLCHAR;
11924     bookRequested = TRUE;
11925 }
11926
11927 void
11928 AboutGameEvent()
11929 {
11930     char *tags = PGNTags(&gameInfo);
11931     TagsPopUp(tags, CmailMsg());
11932     free(tags);
11933 }
11934
11935 /* end button procedures */
11936
11937 void
11938 PrintPosition(fp, move)
11939      FILE *fp;
11940      int move;
11941 {
11942     int i, j;
11943
11944     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11945         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11946             char c = PieceToChar(boards[move][i][j]);
11947             fputc(c == 'x' ? '.' : c, fp);
11948             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11949         }
11950     }
11951     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11952       fprintf(fp, "white to play\n");
11953     else
11954       fprintf(fp, "black to play\n");
11955 }
11956
11957 void
11958 PrintOpponents(fp)
11959      FILE *fp;
11960 {
11961     if (gameInfo.white != NULL) {
11962         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11963     } else {
11964         fprintf(fp, "\n");
11965     }
11966 }
11967
11968 /* Find last component of program's own name, using some heuristics */
11969 void
11970 TidyProgramName(prog, host, buf)
11971      char *prog, *host, buf[MSG_SIZ];
11972 {
11973     char *p, *q;
11974     int local = (strcmp(host, "localhost") == 0);
11975     while (!local && (p = strchr(prog, ';')) != NULL) {
11976         p++;
11977         while (*p == ' ') p++;
11978         prog = p;
11979     }
11980     if (*prog == '"' || *prog == '\'') {
11981         q = strchr(prog + 1, *prog);
11982     } else {
11983         q = strchr(prog, ' ');
11984     }
11985     if (q == NULL) q = prog + strlen(prog);
11986     p = q;
11987     while (p >= prog && *p != '/' && *p != '\\') p--;
11988     p++;
11989     if(p == prog && *p == '"') p++;
11990     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11991     memcpy(buf, p, q - p);
11992     buf[q - p] = NULLCHAR;
11993     if (!local) {
11994         strcat(buf, "@");
11995         strcat(buf, host);
11996     }
11997 }
11998
11999 char *
12000 TimeControlTagValue()
12001 {
12002     char buf[MSG_SIZ];
12003     if (!appData.clockMode) {
12004         strcpy(buf, "-");
12005     } else if (movesPerSession > 0) {
12006         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12007     } else if (timeIncrement == 0) {
12008         sprintf(buf, "%ld", timeControl/1000);
12009     } else {
12010         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12011     }
12012     return StrSave(buf);
12013 }
12014
12015 void
12016 SetGameInfo()
12017 {
12018     /* This routine is used only for certain modes */
12019     VariantClass v = gameInfo.variant;
12020     ClearGameInfo(&gameInfo);
12021     gameInfo.variant = v;
12022
12023     switch (gameMode) {
12024       case MachinePlaysWhite:
12025         gameInfo.event = StrSave( appData.pgnEventHeader );
12026         gameInfo.site = StrSave(HostName());
12027         gameInfo.date = PGNDate();
12028         gameInfo.round = StrSave("-");
12029         gameInfo.white = StrSave(first.tidy);
12030         gameInfo.black = StrSave(UserName());
12031         gameInfo.timeControl = TimeControlTagValue();
12032         break;
12033
12034       case MachinePlaysBlack:
12035         gameInfo.event = StrSave( appData.pgnEventHeader );
12036         gameInfo.site = StrSave(HostName());
12037         gameInfo.date = PGNDate();
12038         gameInfo.round = StrSave("-");
12039         gameInfo.white = StrSave(UserName());
12040         gameInfo.black = StrSave(first.tidy);
12041         gameInfo.timeControl = TimeControlTagValue();
12042         break;
12043
12044       case TwoMachinesPlay:
12045         gameInfo.event = StrSave( appData.pgnEventHeader );
12046         gameInfo.site = StrSave(HostName());
12047         gameInfo.date = PGNDate();
12048         if (matchGame > 0) {
12049             char buf[MSG_SIZ];
12050             sprintf(buf, "%d", matchGame);
12051             gameInfo.round = StrSave(buf);
12052         } else {
12053             gameInfo.round = StrSave("-");
12054         }
12055         if (first.twoMachinesColor[0] == 'w') {
12056             gameInfo.white = StrSave(first.tidy);
12057             gameInfo.black = StrSave(second.tidy);
12058         } else {
12059             gameInfo.white = StrSave(second.tidy);
12060             gameInfo.black = StrSave(first.tidy);
12061         }
12062         gameInfo.timeControl = TimeControlTagValue();
12063         break;
12064
12065       case EditGame:
12066         gameInfo.event = StrSave("Edited game");
12067         gameInfo.site = StrSave(HostName());
12068         gameInfo.date = PGNDate();
12069         gameInfo.round = StrSave("-");
12070         gameInfo.white = StrSave("-");
12071         gameInfo.black = StrSave("-");
12072         break;
12073
12074       case EditPosition:
12075         gameInfo.event = StrSave("Edited position");
12076         gameInfo.site = StrSave(HostName());
12077         gameInfo.date = PGNDate();
12078         gameInfo.round = StrSave("-");
12079         gameInfo.white = StrSave("-");
12080         gameInfo.black = StrSave("-");
12081         break;
12082
12083       case IcsPlayingWhite:
12084       case IcsPlayingBlack:
12085       case IcsObserving:
12086       case IcsExamining:
12087         break;
12088
12089       case PlayFromGameFile:
12090         gameInfo.event = StrSave("Game from non-PGN file");
12091         gameInfo.site = StrSave(HostName());
12092         gameInfo.date = PGNDate();
12093         gameInfo.round = StrSave("-");
12094         gameInfo.white = StrSave("?");
12095         gameInfo.black = StrSave("?");
12096         break;
12097
12098       default:
12099         break;
12100     }
12101 }
12102
12103 void
12104 ReplaceComment(index, text)
12105      int index;
12106      char *text;
12107 {
12108     int len;
12109
12110     while (*text == '\n') text++;
12111     len = strlen(text);
12112     while (len > 0 && text[len - 1] == '\n') len--;
12113
12114     if (commentList[index] != NULL)
12115       free(commentList[index]);
12116
12117     if (len == 0) {
12118         commentList[index] = NULL;
12119         return;
12120     }
12121     commentList[index] = (char *) malloc(len + 2);
12122     strncpy(commentList[index], text, len);
12123     commentList[index][len] = '\n';
12124     commentList[index][len + 1] = NULLCHAR;
12125 }
12126
12127 void
12128 CrushCRs(text)
12129      char *text;
12130 {
12131   char *p = text;
12132   char *q = text;
12133   char ch;
12134
12135   do {
12136     ch = *p++;
12137     if (ch == '\r') continue;
12138     *q++ = ch;
12139   } while (ch != '\0');
12140 }
12141
12142 void
12143 AppendComment(index, text)
12144      int index;
12145      char *text;
12146 {
12147     int oldlen, len;
12148     char *old;
12149
12150     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12151
12152     CrushCRs(text);
12153     while (*text == '\n') text++;
12154     len = strlen(text);
12155     while (len > 0 && text[len - 1] == '\n') len--;
12156
12157     if (len == 0) return;
12158
12159     if (commentList[index] != NULL) {
12160         old = commentList[index];
12161         oldlen = strlen(old);
12162         commentList[index] = (char *) malloc(oldlen + len + 2);
12163         strcpy(commentList[index], old);
12164         free(old);
12165         strncpy(&commentList[index][oldlen], text, len);
12166         commentList[index][oldlen + len] = '\n';
12167         commentList[index][oldlen + len + 1] = NULLCHAR;
12168     } else {
12169         commentList[index] = (char *) malloc(len + 2);
12170         strncpy(commentList[index], text, len);
12171         commentList[index][len] = '\n';
12172         commentList[index][len + 1] = NULLCHAR;
12173     }
12174 }
12175
12176 static char * FindStr( char * text, char * sub_text )
12177 {
12178     char * result = strstr( text, sub_text );
12179
12180     if( result != NULL ) {
12181         result += strlen( sub_text );
12182     }
12183
12184     return result;
12185 }
12186
12187 /* [AS] Try to extract PV info from PGN comment */
12188 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12189 char *GetInfoFromComment( int index, char * text )
12190 {
12191     char * sep = text;
12192
12193     if( text != NULL && index > 0 ) {
12194         int score = 0;
12195         int depth = 0;
12196         int time = -1, sec = 0, deci;
12197         char * s_eval = FindStr( text, "[%eval " );
12198         char * s_emt = FindStr( text, "[%emt " );
12199
12200         if( s_eval != NULL || s_emt != NULL ) {
12201             /* New style */
12202             char delim;
12203
12204             if( s_eval != NULL ) {
12205                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12206                     return text;
12207                 }
12208
12209                 if( delim != ']' ) {
12210                     return text;
12211                 }
12212             }
12213
12214             if( s_emt != NULL ) {
12215             }
12216         }
12217         else {
12218             /* We expect something like: [+|-]nnn.nn/dd */
12219             int score_lo = 0;
12220
12221             sep = strchr( text, '/' );
12222             if( sep == NULL || sep < (text+4) ) {
12223                 return text;
12224             }
12225
12226             time = -1; sec = -1; deci = -1;
12227             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12228                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12229                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12230                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12231                 return text;
12232             }
12233
12234             if( score_lo < 0 || score_lo >= 100 ) {
12235                 return text;
12236             }
12237
12238             if(sec >= 0) time = 600*time + 10*sec; else
12239             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12240
12241             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12242
12243             /* [HGM] PV time: now locate end of PV info */
12244             while( *++sep >= '0' && *sep <= '9'); // strip depth
12245             if(time >= 0)
12246             while( *++sep >= '0' && *sep <= '9'); // strip time
12247             if(sec >= 0)
12248             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12249             if(deci >= 0)
12250             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12251             while(*sep == ' ') sep++;
12252         }
12253
12254         if( depth <= 0 ) {
12255             return text;
12256         }
12257
12258         if( time < 0 ) {
12259             time = -1;
12260         }
12261
12262         pvInfoList[index-1].depth = depth;
12263         pvInfoList[index-1].score = score;
12264         pvInfoList[index-1].time  = 10*time; // centi-sec
12265     }
12266     return sep;
12267 }
12268
12269 void
12270 SendToProgram(message, cps)
12271      char *message;
12272      ChessProgramState *cps;
12273 {
12274     int count, outCount, error;
12275     char buf[MSG_SIZ];
12276
12277     if (cps->pr == NULL) return;
12278     Attention(cps);
12279
12280     if (appData.debugMode) {
12281         TimeMark now;
12282         GetTimeMark(&now);
12283         fprintf(debugFP, "%ld >%-6s: %s",
12284                 SubtractTimeMarks(&now, &programStartTime),
12285                 cps->which, message);
12286     }
12287
12288     count = strlen(message);
12289     outCount = OutputToProcess(cps->pr, message, count, &error);
12290     if (outCount < count && !exiting
12291                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12292         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12293         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12294             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12295                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12296                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12297             } else {
12298                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12299             }
12300             gameInfo.resultDetails = buf;
12301         }
12302         DisplayFatalError(buf, error, 1);
12303     }
12304 }
12305
12306 void
12307 ReceiveFromProgram(isr, closure, message, count, error)
12308      InputSourceRef isr;
12309      VOIDSTAR closure;
12310      char *message;
12311      int count;
12312      int error;
12313 {
12314     char *end_str;
12315     char buf[MSG_SIZ];
12316     ChessProgramState *cps = (ChessProgramState *)closure;
12317
12318     if (isr != cps->isr) return; /* Killed intentionally */
12319     if (count <= 0) {
12320         if (count == 0) {
12321             sprintf(buf,
12322                     _("Error: %s chess program (%s) exited unexpectedly"),
12323                     cps->which, cps->program);
12324         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12325                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12326                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12327                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12328                 } else {
12329                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12330                 }
12331                 gameInfo.resultDetails = buf;
12332             }
12333             RemoveInputSource(cps->isr);
12334             DisplayFatalError(buf, 0, 1);
12335         } else {
12336             sprintf(buf,
12337                     _("Error reading from %s chess program (%s)"),
12338                     cps->which, cps->program);
12339             RemoveInputSource(cps->isr);
12340
12341             /* [AS] Program is misbehaving badly... kill it */
12342             if( count == -2 ) {
12343                 DestroyChildProcess( cps->pr, 9 );
12344                 cps->pr = NoProc;
12345             }
12346
12347             DisplayFatalError(buf, error, 1);
12348         }
12349         return;
12350     }
12351
12352     if ((end_str = strchr(message, '\r')) != NULL)
12353       *end_str = NULLCHAR;
12354     if ((end_str = strchr(message, '\n')) != NULL)
12355       *end_str = NULLCHAR;
12356
12357     if (appData.debugMode) {
12358         TimeMark now; int print = 1;
12359         char *quote = ""; char c; int i;
12360
12361         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12362                 char start = message[0];
12363                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12364                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12365                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12366                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12367                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12368                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12369                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12370                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12371                         { quote = "# "; print = (appData.engineComments == 2); }
12372                 message[0] = start; // restore original message
12373         }
12374         if(print) {
12375                 GetTimeMark(&now);
12376                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12377                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12378                         quote,
12379                         message);
12380         }
12381     }
12382
12383     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12384     if (appData.icsEngineAnalyze) {
12385         if (strstr(message, "whisper") != NULL ||
12386              strstr(message, "kibitz") != NULL ||
12387             strstr(message, "tellics") != NULL) return;
12388     }
12389
12390     HandleMachineMove(message, cps);
12391 }
12392
12393
12394 void
12395 SendTimeControl(cps, mps, tc, inc, sd, st)
12396      ChessProgramState *cps;
12397      int mps, inc, sd, st;
12398      long tc;
12399 {
12400     char buf[MSG_SIZ];
12401     int seconds;
12402
12403     if( timeControl_2 > 0 ) {
12404         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12405             tc = timeControl_2;
12406         }
12407     }
12408     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12409     inc /= cps->timeOdds;
12410     st  /= cps->timeOdds;
12411
12412     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12413
12414     if (st > 0) {
12415       /* Set exact time per move, normally using st command */
12416       if (cps->stKludge) {
12417         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12418         seconds = st % 60;
12419         if (seconds == 0) {
12420           sprintf(buf, "level 1 %d\n", st/60);
12421         } else {
12422           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12423         }
12424       } else {
12425         sprintf(buf, "st %d\n", st);
12426       }
12427     } else {
12428       /* Set conventional or incremental time control, using level command */
12429       if (seconds == 0) {
12430         /* Note old gnuchess bug -- minutes:seconds used to not work.
12431            Fixed in later versions, but still avoid :seconds
12432            when seconds is 0. */
12433         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12434       } else {
12435         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12436                 seconds, inc/1000);
12437       }
12438     }
12439     SendToProgram(buf, cps);
12440
12441     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12442     /* Orthogonally, limit search to given depth */
12443     if (sd > 0) {
12444       if (cps->sdKludge) {
12445         sprintf(buf, "depth\n%d\n", sd);
12446       } else {
12447         sprintf(buf, "sd %d\n", sd);
12448       }
12449       SendToProgram(buf, cps);
12450     }
12451
12452     if(cps->nps > 0) { /* [HGM] nps */
12453         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12454         else {
12455                 sprintf(buf, "nps %d\n", cps->nps);
12456               SendToProgram(buf, cps);
12457         }
12458     }
12459 }
12460
12461 ChessProgramState *WhitePlayer()
12462 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12463 {
12464     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12465        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12466         return &second;
12467     return &first;
12468 }
12469
12470 void
12471 SendTimeRemaining(cps, machineWhite)
12472      ChessProgramState *cps;
12473      int /*boolean*/ machineWhite;
12474 {
12475     char message[MSG_SIZ];
12476     long time, otime;
12477
12478     /* Note: this routine must be called when the clocks are stopped
12479        or when they have *just* been set or switched; otherwise
12480        it will be off by the time since the current tick started.
12481     */
12482     if (machineWhite) {
12483         time = whiteTimeRemaining / 10;
12484         otime = blackTimeRemaining / 10;
12485     } else {
12486         time = blackTimeRemaining / 10;
12487         otime = whiteTimeRemaining / 10;
12488     }
12489     /* [HGM] translate opponent's time by time-odds factor */
12490     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12491     if (appData.debugMode) {
12492         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12493     }
12494
12495     if (time <= 0) time = 1;
12496     if (otime <= 0) otime = 1;
12497
12498     sprintf(message, "time %ld\n", time);
12499     SendToProgram(message, cps);
12500
12501     sprintf(message, "otim %ld\n", otime);
12502     SendToProgram(message, cps);
12503 }
12504
12505 int
12506 BoolFeature(p, name, loc, cps)
12507      char **p;
12508      char *name;
12509      int *loc;
12510      ChessProgramState *cps;
12511 {
12512   char buf[MSG_SIZ];
12513   int len = strlen(name);
12514   int val;
12515   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12516     (*p) += len + 1;
12517     sscanf(*p, "%d", &val);
12518     *loc = (val != 0);
12519     while (**p && **p != ' ') (*p)++;
12520     sprintf(buf, "accepted %s\n", name);
12521     SendToProgram(buf, cps);
12522     return TRUE;
12523   }
12524   return FALSE;
12525 }
12526
12527 int
12528 IntFeature(p, name, loc, cps)
12529      char **p;
12530      char *name;
12531      int *loc;
12532      ChessProgramState *cps;
12533 {
12534   char buf[MSG_SIZ];
12535   int len = strlen(name);
12536   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12537     (*p) += len + 1;
12538     sscanf(*p, "%d", loc);
12539     while (**p && **p != ' ') (*p)++;
12540     sprintf(buf, "accepted %s\n", name);
12541     SendToProgram(buf, cps);
12542     return TRUE;
12543   }
12544   return FALSE;
12545 }
12546
12547 int
12548 StringFeature(p, name, loc, cps)
12549      char **p;
12550      char *name;
12551      char loc[];
12552      ChessProgramState *cps;
12553 {
12554   char buf[MSG_SIZ];
12555   int len = strlen(name);
12556   if (strncmp((*p), name, len) == 0
12557       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12558     (*p) += len + 2;
12559     sscanf(*p, "%[^\"]", loc);
12560     while (**p && **p != '\"') (*p)++;
12561     if (**p == '\"') (*p)++;
12562     sprintf(buf, "accepted %s\n", name);
12563     SendToProgram(buf, cps);
12564     return TRUE;
12565   }
12566   return FALSE;
12567 }
12568
12569 int
12570 ParseOption(Option *opt, ChessProgramState *cps)
12571 // [HGM] options: process the string that defines an engine option, and determine
12572 // name, type, default value, and allowed value range
12573 {
12574         char *p, *q, buf[MSG_SIZ];
12575         int n, min = (-1)<<31, max = 1<<31, def;
12576
12577         if(p = strstr(opt->name, " -spin ")) {
12578             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12579             if(max < min) max = min; // enforce consistency
12580             if(def < min) def = min;
12581             if(def > max) def = max;
12582             opt->value = def;
12583             opt->min = min;
12584             opt->max = max;
12585             opt->type = Spin;
12586         } else if((p = strstr(opt->name, " -slider "))) {
12587             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12588             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12589             if(max < min) max = min; // enforce consistency
12590             if(def < min) def = min;
12591             if(def > max) def = max;
12592             opt->value = def;
12593             opt->min = min;
12594             opt->max = max;
12595             opt->type = Spin; // Slider;
12596         } else if((p = strstr(opt->name, " -string "))) {
12597             opt->textValue = p+9;
12598             opt->type = TextBox;
12599         } else if((p = strstr(opt->name, " -file "))) {
12600             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12601             opt->textValue = p+7;
12602             opt->type = TextBox; // FileName;
12603         } else if((p = strstr(opt->name, " -path "))) {
12604             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12605             opt->textValue = p+7;
12606             opt->type = TextBox; // PathName;
12607         } else if(p = strstr(opt->name, " -check ")) {
12608             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12609             opt->value = (def != 0);
12610             opt->type = CheckBox;
12611         } else if(p = strstr(opt->name, " -combo ")) {
12612             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12613             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12614             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12615             opt->value = n = 0;
12616             while(q = StrStr(q, " /// ")) {
12617                 n++; *q = 0;    // count choices, and null-terminate each of them
12618                 q += 5;
12619                 if(*q == '*') { // remember default, which is marked with * prefix
12620                     q++;
12621                     opt->value = n;
12622                 }
12623                 cps->comboList[cps->comboCnt++] = q;
12624             }
12625             cps->comboList[cps->comboCnt++] = NULL;
12626             opt->max = n + 1;
12627             opt->type = ComboBox;
12628         } else if(p = strstr(opt->name, " -button")) {
12629             opt->type = Button;
12630         } else if(p = strstr(opt->name, " -save")) {
12631             opt->type = SaveButton;
12632         } else return FALSE;
12633         *p = 0; // terminate option name
12634         // now look if the command-line options define a setting for this engine option.
12635         if(cps->optionSettings && cps->optionSettings[0])
12636             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12637         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12638                 sprintf(buf, "option %s", p);
12639                 if(p = strstr(buf, ",")) *p = 0;
12640                 strcat(buf, "\n");
12641                 SendToProgram(buf, cps);
12642         }
12643         return TRUE;
12644 }
12645
12646 void
12647 FeatureDone(cps, val)
12648      ChessProgramState* cps;
12649      int val;
12650 {
12651   DelayedEventCallback cb = GetDelayedEvent();
12652   if ((cb == InitBackEnd3 && cps == &first) ||
12653       (cb == TwoMachinesEventIfReady && cps == &second)) {
12654     CancelDelayedEvent();
12655     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12656   }
12657   cps->initDone = val;
12658 }
12659
12660 /* Parse feature command from engine */
12661 void
12662 ParseFeatures(args, cps)
12663      char* args;
12664      ChessProgramState *cps;
12665 {
12666   char *p = args;
12667   char *q;
12668   int val;
12669   char buf[MSG_SIZ];
12670
12671   for (;;) {
12672     while (*p == ' ') p++;
12673     if (*p == NULLCHAR) return;
12674
12675     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12676     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12677     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12678     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12679     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12680     if (BoolFeature(&p, "reuse", &val, cps)) {
12681       /* Engine can disable reuse, but can't enable it if user said no */
12682       if (!val) cps->reuse = FALSE;
12683       continue;
12684     }
12685     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12686     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12687       if (gameMode == TwoMachinesPlay) {
12688         DisplayTwoMachinesTitle();
12689       } else {
12690         DisplayTitle("");
12691       }
12692       continue;
12693     }
12694     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12695     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12696     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12697     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12698     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12699     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12700     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12701     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12702     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12703     if (IntFeature(&p, "done", &val, cps)) {
12704       FeatureDone(cps, val);
12705       continue;
12706     }
12707     /* Added by Tord: */
12708     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12709     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12710     /* End of additions by Tord */
12711
12712     /* [HGM] added features: */
12713     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12714     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12715     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12716     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12717     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12718     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12719     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12720         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12721             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12722             SendToProgram(buf, cps);
12723             continue;
12724         }
12725         if(cps->nrOptions >= MAX_OPTIONS) {
12726             cps->nrOptions--;
12727             sprintf(buf, "%s engine has too many options\n", cps->which);
12728             DisplayError(buf, 0);
12729         }
12730         continue;
12731     }
12732     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12733     /* End of additions by HGM */
12734
12735     /* unknown feature: complain and skip */
12736     q = p;
12737     while (*q && *q != '=') q++;
12738     sprintf(buf, "rejected %.*s\n", q-p, p);
12739     SendToProgram(buf, cps);
12740     p = q;
12741     if (*p == '=') {
12742       p++;
12743       if (*p == '\"') {
12744         p++;
12745         while (*p && *p != '\"') p++;
12746         if (*p == '\"') p++;
12747       } else {
12748         while (*p && *p != ' ') p++;
12749       }
12750     }
12751   }
12752
12753 }
12754
12755 void
12756 PeriodicUpdatesEvent(newState)
12757      int newState;
12758 {
12759     if (newState == appData.periodicUpdates)
12760       return;
12761
12762     appData.periodicUpdates=newState;
12763
12764     /* Display type changes, so update it now */
12765     DisplayAnalysis();
12766
12767     /* Get the ball rolling again... */
12768     if (newState) {
12769         AnalysisPeriodicEvent(1);
12770         StartAnalysisClock();
12771     }
12772 }
12773
12774 void
12775 PonderNextMoveEvent(newState)
12776      int newState;
12777 {
12778     if (newState == appData.ponderNextMove) return;
12779     if (gameMode == EditPosition) EditPositionDone();
12780     if (newState) {
12781         SendToProgram("hard\n", &first);
12782         if (gameMode == TwoMachinesPlay) {
12783             SendToProgram("hard\n", &second);
12784         }
12785     } else {
12786         SendToProgram("easy\n", &first);
12787         thinkOutput[0] = NULLCHAR;
12788         if (gameMode == TwoMachinesPlay) {
12789             SendToProgram("easy\n", &second);
12790         }
12791     }
12792     appData.ponderNextMove = newState;
12793 }
12794
12795 void
12796 NewSettingEvent(option, command, value)
12797      char *command;
12798      int option, value;
12799 {
12800     char buf[MSG_SIZ];
12801
12802     if (gameMode == EditPosition) EditPositionDone();
12803     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12804     SendToProgram(buf, &first);
12805     if (gameMode == TwoMachinesPlay) {
12806         SendToProgram(buf, &second);
12807     }
12808 }
12809
12810 void
12811 ShowThinkingEvent()
12812 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12813 {
12814     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12815     int newState = appData.showThinking
12816         // [HGM] thinking: other features now need thinking output as well
12817         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12818
12819     if (oldState == newState) return;
12820     oldState = newState;
12821     if (gameMode == EditPosition) EditPositionDone();
12822     if (oldState) {
12823         SendToProgram("post\n", &first);
12824         if (gameMode == TwoMachinesPlay) {
12825             SendToProgram("post\n", &second);
12826         }
12827     } else {
12828         SendToProgram("nopost\n", &first);
12829         thinkOutput[0] = NULLCHAR;
12830         if (gameMode == TwoMachinesPlay) {
12831             SendToProgram("nopost\n", &second);
12832         }
12833     }
12834 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12835 }
12836
12837 void
12838 AskQuestionEvent(title, question, replyPrefix, which)
12839      char *title; char *question; char *replyPrefix; char *which;
12840 {
12841   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12842   if (pr == NoProc) return;
12843   AskQuestion(title, question, replyPrefix, pr);
12844 }
12845
12846 void
12847 DisplayMove(moveNumber)
12848      int moveNumber;
12849 {
12850     char message[MSG_SIZ];
12851     char res[MSG_SIZ];
12852     char cpThinkOutput[MSG_SIZ];
12853
12854     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12855
12856     if (moveNumber == forwardMostMove - 1 ||
12857         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12858
12859         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12860
12861         if (strchr(cpThinkOutput, '\n')) {
12862             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12863         }
12864     } else {
12865         *cpThinkOutput = NULLCHAR;
12866     }
12867
12868     /* [AS] Hide thinking from human user */
12869     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12870         *cpThinkOutput = NULLCHAR;
12871         if( thinkOutput[0] != NULLCHAR ) {
12872             int i;
12873
12874             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12875                 cpThinkOutput[i] = '.';
12876             }
12877             cpThinkOutput[i] = NULLCHAR;
12878             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12879         }
12880     }
12881
12882     if (moveNumber == forwardMostMove - 1 &&
12883         gameInfo.resultDetails != NULL) {
12884         if (gameInfo.resultDetails[0] == NULLCHAR) {
12885             sprintf(res, " %s", PGNResult(gameInfo.result));
12886         } else {
12887             sprintf(res, " {%s} %s",
12888                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12889         }
12890     } else {
12891         res[0] = NULLCHAR;
12892     }
12893
12894     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12895         DisplayMessage(res, cpThinkOutput);
12896     } else {
12897         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12898                 WhiteOnMove(moveNumber) ? " " : ".. ",
12899                 parseList[moveNumber], res);
12900         DisplayMessage(message, cpThinkOutput);
12901     }
12902 }
12903
12904 void
12905 DisplayAnalysisText(text)
12906      char *text;
12907 {
12908     char buf[MSG_SIZ];
12909
12910     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12911                || appData.icsEngineAnalyze) {
12912         sprintf(buf, "Analysis (%s)", first.tidy);
12913         AnalysisPopUp(buf, text);
12914     }
12915 }
12916
12917 static int
12918 only_one_move(str)
12919      char *str;
12920 {
12921     while (*str && isspace(*str)) ++str;
12922     while (*str && !isspace(*str)) ++str;
12923     if (!*str) return 1;
12924     while (*str && isspace(*str)) ++str;
12925     if (!*str) return 1;
12926     return 0;
12927 }
12928
12929 void
12930 DisplayAnalysis()
12931 {
12932     char buf[MSG_SIZ];
12933     char lst[MSG_SIZ / 2];
12934     double nps;
12935     static char *xtra[] = { "", " (--)", " (++)" };
12936     int h, m, s, cs;
12937
12938     if (programStats.time == 0) {
12939         programStats.time = 1;
12940     }
12941
12942     if (programStats.got_only_move) {
12943         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12944     } else {
12945         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12946
12947         nps = (u64ToDouble(programStats.nodes) /
12948              ((double)programStats.time /100.0));
12949
12950         cs = programStats.time % 100;
12951         s = programStats.time / 100;
12952         h = (s / (60*60));
12953         s = s - h*60*60;
12954         m = (s/60);
12955         s = s - m*60;
12956
12957         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12958           if (programStats.move_name[0] != NULLCHAR) {
12959             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12960                     programStats.depth,
12961                     programStats.nr_moves-programStats.moves_left,
12962                     programStats.nr_moves, programStats.move_name,
12963                     ((float)programStats.score)/100.0, lst,
12964                     only_one_move(lst)?
12965                     xtra[programStats.got_fail] : "",
12966                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12967           } else {
12968             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12969                     programStats.depth,
12970                     programStats.nr_moves-programStats.moves_left,
12971                     programStats.nr_moves, ((float)programStats.score)/100.0,
12972                     lst,
12973                     only_one_move(lst)?
12974                     xtra[programStats.got_fail] : "",
12975                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12976           }
12977         } else {
12978             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12979                     programStats.depth,
12980                     ((float)programStats.score)/100.0,
12981                     lst,
12982                     only_one_move(lst)?
12983                     xtra[programStats.got_fail] : "",
12984                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12985         }
12986     }
12987     DisplayAnalysisText(buf);
12988 }
12989
12990 void
12991 DisplayComment(moveNumber, text)
12992      int moveNumber;
12993      char *text;
12994 {
12995     char title[MSG_SIZ];
12996     char buf[8000]; // comment can be long!
12997     int score, depth;
12998
12999     if( appData.autoDisplayComment ) {
13000         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13001             strcpy(title, "Comment");
13002         } else {
13003             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13004                     WhiteOnMove(moveNumber) ? " " : ".. ",
13005                     parseList[moveNumber]);
13006         }
13007         // [HGM] PV info: display PV info together with (or as) comment
13008         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13009             if(text == NULL) text = "";
13010             score = pvInfoList[moveNumber].score;
13011             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13012                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13013             text = buf;
13014         }
13015     } else title[0] = 0;
13016
13017     if (text != NULL)
13018         CommentPopUp(title, text);
13019 }
13020
13021 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13022  * might be busy thinking or pondering.  It can be omitted if your
13023  * gnuchess is configured to stop thinking immediately on any user
13024  * input.  However, that gnuchess feature depends on the FIONREAD
13025  * ioctl, which does not work properly on some flavors of Unix.
13026  */
13027 void
13028 Attention(cps)
13029      ChessProgramState *cps;
13030 {
13031 #if ATTENTION
13032     if (!cps->useSigint) return;
13033     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13034     switch (gameMode) {
13035       case MachinePlaysWhite:
13036       case MachinePlaysBlack:
13037       case TwoMachinesPlay:
13038       case IcsPlayingWhite:
13039       case IcsPlayingBlack:
13040       case AnalyzeMode:
13041       case AnalyzeFile:
13042         /* Skip if we know it isn't thinking */
13043         if (!cps->maybeThinking) return;
13044         if (appData.debugMode)
13045           fprintf(debugFP, "Interrupting %s\n", cps->which);
13046         InterruptChildProcess(cps->pr);
13047         cps->maybeThinking = FALSE;
13048         break;
13049       default:
13050         break;
13051     }
13052 #endif /*ATTENTION*/
13053 }
13054
13055 int
13056 CheckFlags()
13057 {
13058     if (whiteTimeRemaining <= 0) {
13059         if (!whiteFlag) {
13060             whiteFlag = TRUE;
13061             if (appData.icsActive) {
13062                 if (appData.autoCallFlag &&
13063                     gameMode == IcsPlayingBlack && !blackFlag) {
13064                   SendToICS(ics_prefix);
13065                   SendToICS("flag\n");
13066                 }
13067             } else {
13068                 if (blackFlag) {
13069                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13070                 } else {
13071                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13072                     if (appData.autoCallFlag) {
13073                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13074                         return TRUE;
13075                     }
13076                 }
13077             }
13078         }
13079     }
13080     if (blackTimeRemaining <= 0) {
13081         if (!blackFlag) {
13082             blackFlag = TRUE;
13083             if (appData.icsActive) {
13084                 if (appData.autoCallFlag &&
13085                     gameMode == IcsPlayingWhite && !whiteFlag) {
13086                   SendToICS(ics_prefix);
13087                   SendToICS("flag\n");
13088                 }
13089             } else {
13090                 if (whiteFlag) {
13091                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13092                 } else {
13093                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13094                     if (appData.autoCallFlag) {
13095                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13096                         return TRUE;
13097                     }
13098                 }
13099             }
13100         }
13101     }
13102     return FALSE;
13103 }
13104
13105 void
13106 CheckTimeControl()
13107 {
13108     if (!appData.clockMode || appData.icsActive ||
13109         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13110
13111     /*
13112      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13113      */
13114     if ( !WhiteOnMove(forwardMostMove) )
13115         /* White made time control */
13116         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13117         /* [HGM] time odds: correct new time quota for time odds! */
13118                                             / WhitePlayer()->timeOdds;
13119       else
13120         /* Black made time control */
13121         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13122                                             / WhitePlayer()->other->timeOdds;
13123 }
13124
13125 void
13126 DisplayBothClocks()
13127 {
13128     int wom = gameMode == EditPosition ?
13129       !blackPlaysFirst : WhiteOnMove(currentMove);
13130     DisplayWhiteClock(whiteTimeRemaining, wom);
13131     DisplayBlackClock(blackTimeRemaining, !wom);
13132 }
13133
13134
13135 /* Timekeeping seems to be a portability nightmare.  I think everyone
13136    has ftime(), but I'm really not sure, so I'm including some ifdefs
13137    to use other calls if you don't.  Clocks will be less accurate if
13138    you have neither ftime nor gettimeofday.
13139 */
13140
13141 /* VS 2008 requires the #include outside of the function */
13142 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13143 #include <sys/timeb.h>
13144 #endif
13145
13146 /* Get the current time as a TimeMark */
13147 void
13148 GetTimeMark(tm)
13149      TimeMark *tm;
13150 {
13151 #if HAVE_GETTIMEOFDAY
13152
13153     struct timeval timeVal;
13154     struct timezone timeZone;
13155
13156     gettimeofday(&timeVal, &timeZone);
13157     tm->sec = (long) timeVal.tv_sec;
13158     tm->ms = (int) (timeVal.tv_usec / 1000L);
13159
13160 #else /*!HAVE_GETTIMEOFDAY*/
13161 #if HAVE_FTIME
13162
13163 // include <sys/timeb.h> / moved to just above start of function
13164     struct timeb timeB;
13165
13166     ftime(&timeB);
13167     tm->sec = (long) timeB.time;
13168     tm->ms = (int) timeB.millitm;
13169
13170 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13171     tm->sec = (long) time(NULL);
13172     tm->ms = 0;
13173 #endif
13174 #endif
13175 }
13176
13177 /* Return the difference in milliseconds between two
13178    time marks.  We assume the difference will fit in a long!
13179 */
13180 long
13181 SubtractTimeMarks(tm2, tm1)
13182      TimeMark *tm2, *tm1;
13183 {
13184     return 1000L*(tm2->sec - tm1->sec) +
13185            (long) (tm2->ms - tm1->ms);
13186 }
13187
13188
13189 /*
13190  * Code to manage the game clocks.
13191  *
13192  * In tournament play, black starts the clock and then white makes a move.
13193  * We give the human user a slight advantage if he is playing white---the
13194  * clocks don't run until he makes his first move, so it takes zero time.
13195  * Also, we don't account for network lag, so we could get out of sync
13196  * with GNU Chess's clock -- but then, referees are always right.
13197  */
13198
13199 static TimeMark tickStartTM;
13200 static long intendedTickLength;
13201
13202 long
13203 NextTickLength(timeRemaining)
13204      long timeRemaining;
13205 {
13206     long nominalTickLength, nextTickLength;
13207
13208     if (timeRemaining > 0L && timeRemaining <= 10000L)
13209       nominalTickLength = 100L;
13210     else
13211       nominalTickLength = 1000L;
13212     nextTickLength = timeRemaining % nominalTickLength;
13213     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13214
13215     return nextTickLength;
13216 }
13217
13218 /* Adjust clock one minute up or down */
13219 void
13220 AdjustClock(Boolean which, int dir)
13221 {
13222     if(which) blackTimeRemaining += 60000*dir;
13223     else      whiteTimeRemaining += 60000*dir;
13224     DisplayBothClocks();
13225 }
13226
13227 /* Stop clocks and reset to a fresh time control */
13228 void
13229 ResetClocks()
13230 {
13231     (void) StopClockTimer();
13232     if (appData.icsActive) {
13233         whiteTimeRemaining = blackTimeRemaining = 0;
13234     } else { /* [HGM] correct new time quote for time odds */
13235         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13236         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13237     }
13238     if (whiteFlag || blackFlag) {
13239         DisplayTitle("");
13240         whiteFlag = blackFlag = FALSE;
13241     }
13242     DisplayBothClocks();
13243 }
13244
13245 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13246
13247 /* Decrement running clock by amount of time that has passed */
13248 void
13249 DecrementClocks()
13250 {
13251     long timeRemaining;
13252     long lastTickLength, fudge;
13253     TimeMark now;
13254
13255     if (!appData.clockMode) return;
13256     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13257
13258     GetTimeMark(&now);
13259
13260     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13261
13262     /* Fudge if we woke up a little too soon */
13263     fudge = intendedTickLength - lastTickLength;
13264     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13265
13266     if (WhiteOnMove(forwardMostMove)) {
13267         if(whiteNPS >= 0) lastTickLength = 0;
13268         timeRemaining = whiteTimeRemaining -= lastTickLength;
13269         DisplayWhiteClock(whiteTimeRemaining - fudge,
13270                           WhiteOnMove(currentMove));
13271     } else {
13272         if(blackNPS >= 0) lastTickLength = 0;
13273         timeRemaining = blackTimeRemaining -= lastTickLength;
13274         DisplayBlackClock(blackTimeRemaining - fudge,
13275                           !WhiteOnMove(currentMove));
13276     }
13277
13278     if (CheckFlags()) return;
13279
13280     tickStartTM = now;
13281     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13282     StartClockTimer(intendedTickLength);
13283
13284     /* if the time remaining has fallen below the alarm threshold, sound the
13285      * alarm. if the alarm has sounded and (due to a takeback or time control
13286      * with increment) the time remaining has increased to a level above the
13287      * threshold, reset the alarm so it can sound again.
13288      */
13289
13290     if (appData.icsActive && appData.icsAlarm) {
13291
13292         /* make sure we are dealing with the user's clock */
13293         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13294                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13295            )) return;
13296
13297         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13298             alarmSounded = FALSE;
13299         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13300             PlayAlarmSound();
13301             alarmSounded = TRUE;
13302         }
13303     }
13304 }
13305
13306
13307 /* A player has just moved, so stop the previously running
13308    clock and (if in clock mode) start the other one.
13309    We redisplay both clocks in case we're in ICS mode, because
13310    ICS gives us an update to both clocks after every move.
13311    Note that this routine is called *after* forwardMostMove
13312    is updated, so the last fractional tick must be subtracted
13313    from the color that is *not* on move now.
13314 */
13315 void
13316 SwitchClocks()
13317 {
13318     long lastTickLength;
13319     TimeMark now;
13320     int flagged = FALSE;
13321
13322     GetTimeMark(&now);
13323
13324     if (StopClockTimer() && appData.clockMode) {
13325         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13326         if (WhiteOnMove(forwardMostMove)) {
13327             if(blackNPS >= 0) lastTickLength = 0;
13328             blackTimeRemaining -= lastTickLength;
13329            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13330 //         if(pvInfoList[forwardMostMove-1].time == -1)
13331                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13332                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13333         } else {
13334            if(whiteNPS >= 0) lastTickLength = 0;
13335            whiteTimeRemaining -= lastTickLength;
13336            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13337 //         if(pvInfoList[forwardMostMove-1].time == -1)
13338                  pvInfoList[forwardMostMove-1].time =
13339                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13340         }
13341         flagged = CheckFlags();
13342     }
13343     CheckTimeControl();
13344
13345     if (flagged || !appData.clockMode) return;
13346
13347     switch (gameMode) {
13348       case MachinePlaysBlack:
13349       case MachinePlaysWhite:
13350       case BeginningOfGame:
13351         if (pausing) return;
13352         break;
13353
13354       case EditGame:
13355       case PlayFromGameFile:
13356       case IcsExamining:
13357         return;
13358
13359       default:
13360         break;
13361     }
13362
13363     tickStartTM = now;
13364     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13365       whiteTimeRemaining : blackTimeRemaining);
13366     StartClockTimer(intendedTickLength);
13367 }
13368
13369
13370 /* Stop both clocks */
13371 void
13372 StopClocks()
13373 {
13374     long lastTickLength;
13375     TimeMark now;
13376
13377     if (!StopClockTimer()) return;
13378     if (!appData.clockMode) return;
13379
13380     GetTimeMark(&now);
13381
13382     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13383     if (WhiteOnMove(forwardMostMove)) {
13384         if(whiteNPS >= 0) lastTickLength = 0;
13385         whiteTimeRemaining -= lastTickLength;
13386         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13387     } else {
13388         if(blackNPS >= 0) lastTickLength = 0;
13389         blackTimeRemaining -= lastTickLength;
13390         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13391     }
13392     CheckFlags();
13393 }
13394
13395 /* Start clock of player on move.  Time may have been reset, so
13396    if clock is already running, stop and restart it. */
13397 void
13398 StartClocks()
13399 {
13400     (void) StopClockTimer(); /* in case it was running already */
13401     DisplayBothClocks();
13402     if (CheckFlags()) return;
13403
13404     if (!appData.clockMode) return;
13405     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13406
13407     GetTimeMark(&tickStartTM);
13408     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13409       whiteTimeRemaining : blackTimeRemaining);
13410
13411    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13412     whiteNPS = blackNPS = -1;
13413     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13414        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13415         whiteNPS = first.nps;
13416     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13417        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13418         blackNPS = first.nps;
13419     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13420         whiteNPS = second.nps;
13421     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13422         blackNPS = second.nps;
13423     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13424
13425     StartClockTimer(intendedTickLength);
13426 }
13427
13428 char *
13429 TimeString(ms)
13430      long ms;
13431 {
13432     long second, minute, hour, day;
13433     char *sign = "";
13434     static char buf[32];
13435
13436     if (ms > 0 && ms <= 9900) {
13437       /* convert milliseconds to tenths, rounding up */
13438       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13439
13440       sprintf(buf, " %03.1f ", tenths/10.0);
13441       return buf;
13442     }
13443
13444     /* convert milliseconds to seconds, rounding up */
13445     /* use floating point to avoid strangeness of integer division
13446        with negative dividends on many machines */
13447     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13448
13449     if (second < 0) {
13450         sign = "-";
13451         second = -second;
13452     }
13453
13454     day = second / (60 * 60 * 24);
13455     second = second % (60 * 60 * 24);
13456     hour = second / (60 * 60);
13457     second = second % (60 * 60);
13458     minute = second / 60;
13459     second = second % 60;
13460
13461     if (day > 0)
13462       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13463               sign, day, hour, minute, second);
13464     else if (hour > 0)
13465       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13466     else
13467       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13468
13469     return buf;
13470 }
13471
13472
13473 /*
13474  * This is necessary because some C libraries aren't ANSI C compliant yet.
13475  */
13476 char *
13477 StrStr(string, match)
13478      char *string, *match;
13479 {
13480     int i, length;
13481
13482     length = strlen(match);
13483
13484     for (i = strlen(string) - length; i >= 0; i--, string++)
13485       if (!strncmp(match, string, length))
13486         return string;
13487
13488     return NULL;
13489 }
13490
13491 char *
13492 StrCaseStr(string, match)
13493      char *string, *match;
13494 {
13495     int i, j, length;
13496
13497     length = strlen(match);
13498
13499     for (i = strlen(string) - length; i >= 0; i--, string++) {
13500         for (j = 0; j < length; j++) {
13501             if (ToLower(match[j]) != ToLower(string[j]))
13502               break;
13503         }
13504         if (j == length) return string;
13505     }
13506
13507     return NULL;
13508 }
13509
13510 #ifndef _amigados
13511 int
13512 StrCaseCmp(s1, s2)
13513      char *s1, *s2;
13514 {
13515     char c1, c2;
13516
13517     for (;;) {
13518         c1 = ToLower(*s1++);
13519         c2 = ToLower(*s2++);
13520         if (c1 > c2) return 1;
13521         if (c1 < c2) return -1;
13522         if (c1 == NULLCHAR) return 0;
13523     }
13524 }
13525
13526
13527 int
13528 ToLower(c)
13529      int c;
13530 {
13531     return isupper(c) ? tolower(c) : c;
13532 }
13533
13534
13535 int
13536 ToUpper(c)
13537      int c;
13538 {
13539     return islower(c) ? toupper(c) : c;
13540 }
13541 #endif /* !_amigados    */
13542
13543 char *
13544 StrSave(s)
13545      char *s;
13546 {
13547     char *ret;
13548
13549     if ((ret = (char *) malloc(strlen(s) + 1))) {
13550         strcpy(ret, s);
13551     }
13552     return ret;
13553 }
13554
13555 char *
13556 StrSavePtr(s, savePtr)
13557      char *s, **savePtr;
13558 {
13559     if (*savePtr) {
13560         free(*savePtr);
13561     }
13562     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13563         strcpy(*savePtr, s);
13564     }
13565     return(*savePtr);
13566 }
13567
13568 char *
13569 PGNDate()
13570 {
13571     time_t clock;
13572     struct tm *tm;
13573     char buf[MSG_SIZ];
13574
13575     clock = time((time_t *)NULL);
13576     tm = localtime(&clock);
13577     sprintf(buf, "%04d.%02d.%02d",
13578             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13579     return StrSave(buf);
13580 }
13581
13582
13583 char *
13584 PositionToFEN(move, overrideCastling)
13585      int move;
13586      char *overrideCastling;
13587 {
13588     int i, j, fromX, fromY, toX, toY;
13589     int whiteToPlay;
13590     char buf[128];
13591     char *p, *q;
13592     int emptycount;
13593     ChessSquare piece;
13594
13595     whiteToPlay = (gameMode == EditPosition) ?
13596       !blackPlaysFirst : (move % 2 == 0);
13597     p = buf;
13598
13599     /* Piece placement data */
13600     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13601         emptycount = 0;
13602         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13603             if (boards[move][i][j] == EmptySquare) {
13604                 emptycount++;
13605             } else { ChessSquare piece = boards[move][i][j];
13606                 if (emptycount > 0) {
13607                     if(emptycount<10) /* [HGM] can be >= 10 */
13608                         *p++ = '0' + emptycount;
13609                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13610                     emptycount = 0;
13611                 }
13612                 if(PieceToChar(piece) == '+') {
13613                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13614                     *p++ = '+';
13615                     piece = (ChessSquare)(DEMOTED piece);
13616                 }
13617                 *p++ = PieceToChar(piece);
13618                 if(p[-1] == '~') {
13619                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13620                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13621                     *p++ = '~';
13622                 }
13623             }
13624         }
13625         if (emptycount > 0) {
13626             if(emptycount<10) /* [HGM] can be >= 10 */
13627                 *p++ = '0' + emptycount;
13628             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13629             emptycount = 0;
13630         }
13631         *p++ = '/';
13632     }
13633     *(p - 1) = ' ';
13634
13635     /* [HGM] print Crazyhouse or Shogi holdings */
13636     if( gameInfo.holdingsWidth ) {
13637         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13638         q = p;
13639         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13640             piece = boards[move][i][BOARD_WIDTH-1];
13641             if( piece != EmptySquare )
13642               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13643                   *p++ = PieceToChar(piece);
13644         }
13645         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13646             piece = boards[move][BOARD_HEIGHT-i-1][0];
13647             if( piece != EmptySquare )
13648               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13649                   *p++ = PieceToChar(piece);
13650         }
13651
13652         if( q == p ) *p++ = '-';
13653         *p++ = ']';
13654         *p++ = ' ';
13655     }
13656
13657     /* Active color */
13658     *p++ = whiteToPlay ? 'w' : 'b';
13659     *p++ = ' ';
13660
13661   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13662     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13663   } else {
13664   if(nrCastlingRights) {
13665      q = p;
13666      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13667        /* [HGM] write directly from rights */
13668            if(castlingRights[move][2] >= 0 &&
13669               castlingRights[move][0] >= 0   )
13670                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13671            if(castlingRights[move][2] >= 0 &&
13672               castlingRights[move][1] >= 0   )
13673                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13674            if(castlingRights[move][5] >= 0 &&
13675               castlingRights[move][3] >= 0   )
13676                 *p++ = castlingRights[move][3] + AAA;
13677            if(castlingRights[move][5] >= 0 &&
13678               castlingRights[move][4] >= 0   )
13679                 *p++ = castlingRights[move][4] + AAA;
13680      } else {
13681
13682         /* [HGM] write true castling rights */
13683         if( nrCastlingRights == 6 ) {
13684             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13685                castlingRights[move][2] >= 0  ) *p++ = 'K';
13686             if(castlingRights[move][1] == BOARD_LEFT &&
13687                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13688             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13689                castlingRights[move][5] >= 0  ) *p++ = 'k';
13690             if(castlingRights[move][4] == BOARD_LEFT &&
13691                castlingRights[move][5] >= 0  ) *p++ = 'q';
13692         }
13693      }
13694      if (q == p) *p++ = '-'; /* No castling rights */
13695      *p++ = ' ';
13696   }
13697
13698   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13699      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13700     /* En passant target square */
13701     if (move > backwardMostMove) {
13702         fromX = moveList[move - 1][0] - AAA;
13703         fromY = moveList[move - 1][1] - ONE;
13704         toX = moveList[move - 1][2] - AAA;
13705         toY = moveList[move - 1][3] - ONE;
13706         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13707             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13708             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13709             fromX == toX) {
13710             /* 2-square pawn move just happened */
13711             *p++ = toX + AAA;
13712             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13713         } else {
13714             *p++ = '-';
13715         }
13716     } else {
13717         *p++ = '-';
13718     }
13719     *p++ = ' ';
13720   }
13721   }
13722
13723     /* [HGM] find reversible plies */
13724     {   int i = 0, j=move;
13725
13726         if (appData.debugMode) { int k;
13727             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13728             for(k=backwardMostMove; k<=forwardMostMove; k++)
13729                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13730
13731         }
13732
13733         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13734         if( j == backwardMostMove ) i += initialRulePlies;
13735         sprintf(p, "%d ", i);
13736         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13737     }
13738     /* Fullmove number */
13739     sprintf(p, "%d", (move / 2) + 1);
13740
13741     return StrSave(buf);
13742 }
13743
13744 Boolean
13745 ParseFEN(board, blackPlaysFirst, fen)
13746     Board board;
13747      int *blackPlaysFirst;
13748      char *fen;
13749 {
13750     int i, j;
13751     char *p;
13752     int emptycount;
13753     ChessSquare piece;
13754
13755     p = fen;
13756
13757     /* [HGM] by default clear Crazyhouse holdings, if present */
13758     if(gameInfo.holdingsWidth) {
13759        for(i=0; i<BOARD_HEIGHT; i++) {
13760            board[i][0]             = EmptySquare; /* black holdings */
13761            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13762            board[i][1]             = (ChessSquare) 0; /* black counts */
13763            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13764        }
13765     }
13766
13767     /* Piece placement data */
13768     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13769         j = 0;
13770         for (;;) {
13771             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13772                 if (*p == '/') p++;
13773                 emptycount = gameInfo.boardWidth - j;
13774                 while (emptycount--)
13775                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13776                 break;
13777 #if(BOARD_SIZE >= 10)
13778             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13779                 p++; emptycount=10;
13780                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13781                 while (emptycount--)
13782                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13783 #endif
13784             } else if (isdigit(*p)) {
13785                 emptycount = *p++ - '0';
13786                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13787                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13788                 while (emptycount--)
13789                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13790             } else if (*p == '+' || isalpha(*p)) {
13791                 if (j >= gameInfo.boardWidth) return FALSE;
13792                 if(*p=='+') {
13793                     piece = CharToPiece(*++p);
13794                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13795                     piece = (ChessSquare) (PROMOTED piece ); p++;
13796                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13797                 } else piece = CharToPiece(*p++);
13798
13799                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13800                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13801                     piece = (ChessSquare) (PROMOTED piece);
13802                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13803                     p++;
13804                 }
13805                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13806             } else {
13807                 return FALSE;
13808             }
13809         }
13810     }
13811     while (*p == '/' || *p == ' ') p++;
13812
13813     /* [HGM] look for Crazyhouse holdings here */
13814     while(*p==' ') p++;
13815     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13816         if(*p == '[') p++;
13817         if(*p == '-' ) *p++; /* empty holdings */ else {
13818             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13819             /* if we would allow FEN reading to set board size, we would   */
13820             /* have to add holdings and shift the board read so far here   */
13821             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13822                 *p++;
13823                 if((int) piece >= (int) BlackPawn ) {
13824                     i = (int)piece - (int)BlackPawn;
13825                     i = PieceToNumber((ChessSquare)i);
13826                     if( i >= gameInfo.holdingsSize ) return FALSE;
13827                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13828                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13829                 } else {
13830                     i = (int)piece - (int)WhitePawn;
13831                     i = PieceToNumber((ChessSquare)i);
13832                     if( i >= gameInfo.holdingsSize ) return FALSE;
13833                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13834                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13835                 }
13836             }
13837         }
13838         if(*p == ']') *p++;
13839     }
13840
13841     while(*p == ' ') p++;
13842
13843     /* Active color */
13844     switch (*p++) {
13845       case 'w':
13846         *blackPlaysFirst = FALSE;
13847         break;
13848       case 'b':
13849         *blackPlaysFirst = TRUE;
13850         break;
13851       default:
13852         return FALSE;
13853     }
13854
13855     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13856     /* return the extra info in global variiables             */
13857
13858     /* set defaults in case FEN is incomplete */
13859     FENepStatus = EP_UNKNOWN;
13860     for(i=0; i<nrCastlingRights; i++ ) {
13861         FENcastlingRights[i] =
13862             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13863     }   /* assume possible unless obviously impossible */
13864     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13865     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13866     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13867     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13868     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13869     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13870     FENrulePlies = 0;
13871
13872     while(*p==' ') p++;
13873     if(nrCastlingRights) {
13874       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13875           /* castling indicator present, so default becomes no castlings */
13876           for(i=0; i<nrCastlingRights; i++ ) {
13877                  FENcastlingRights[i] = -1;
13878           }
13879       }
13880       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13881              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13882              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13883              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13884         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13885
13886         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13887             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13888             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13889         }
13890         switch(c) {
13891           case'K':
13892               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13893               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13894               FENcastlingRights[2] = whiteKingFile;
13895               break;
13896           case'Q':
13897               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13898               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13899               FENcastlingRights[2] = whiteKingFile;
13900               break;
13901           case'k':
13902               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13903               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13904               FENcastlingRights[5] = blackKingFile;
13905               break;
13906           case'q':
13907               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13908               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13909               FENcastlingRights[5] = blackKingFile;
13910           case '-':
13911               break;
13912           default: /* FRC castlings */
13913               if(c >= 'a') { /* black rights */
13914                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13915                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13916                   if(i == BOARD_RGHT) break;
13917                   FENcastlingRights[5] = i;
13918                   c -= AAA;
13919                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13920                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13921                   if(c > i)
13922                       FENcastlingRights[3] = c;
13923                   else
13924                       FENcastlingRights[4] = c;
13925               } else { /* white rights */
13926                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13927                     if(board[0][i] == WhiteKing) break;
13928                   if(i == BOARD_RGHT) break;
13929                   FENcastlingRights[2] = i;
13930                   c -= AAA - 'a' + 'A';
13931                   if(board[0][c] >= WhiteKing) break;
13932                   if(c > i)
13933                       FENcastlingRights[0] = c;
13934                   else
13935                       FENcastlingRights[1] = c;
13936               }
13937         }
13938       }
13939     if (appData.debugMode) {
13940         fprintf(debugFP, "FEN castling rights:");
13941         for(i=0; i<nrCastlingRights; i++)
13942         fprintf(debugFP, " %d", FENcastlingRights[i]);
13943         fprintf(debugFP, "\n");
13944     }
13945
13946       while(*p==' ') p++;
13947     }
13948
13949     /* read e.p. field in games that know e.p. capture */
13950     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13951        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13952       if(*p=='-') {
13953         p++; FENepStatus = EP_NONE;
13954       } else {
13955          char c = *p++ - AAA;
13956
13957          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13958          if(*p >= '0' && *p <='9') *p++;
13959          FENepStatus = c;
13960       }
13961     }
13962
13963
13964     if(sscanf(p, "%d", &i) == 1) {
13965         FENrulePlies = i; /* 50-move ply counter */
13966         /* (The move number is still ignored)    */
13967     }
13968
13969     return TRUE;
13970 }
13971
13972 void
13973 EditPositionPasteFEN(char *fen)
13974 {
13975   if (fen != NULL) {
13976     Board initial_position;
13977
13978     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13979       DisplayError(_("Bad FEN position in clipboard"), 0);
13980       return ;
13981     } else {
13982       int savedBlackPlaysFirst = blackPlaysFirst;
13983       EditPositionEvent();
13984       blackPlaysFirst = savedBlackPlaysFirst;
13985       CopyBoard(boards[0], initial_position);
13986           /* [HGM] copy FEN attributes as well */
13987           {   int i;
13988               initialRulePlies = FENrulePlies;
13989               epStatus[0] = FENepStatus;
13990               for( i=0; i<nrCastlingRights; i++ )
13991                   castlingRights[0][i] = FENcastlingRights[i];
13992           }
13993       EditPositionDone();
13994       DisplayBothClocks();
13995       DrawPosition(FALSE, boards[currentMove]);
13996     }
13997   }
13998 }