Merge branch 'master' into gtk
[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 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 #else
135 # define _(s) (s)
136 # define N_(s) s
137 #endif
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187 void DisplayAnalysis P((void));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 int opponentKibitzes;
247 int lastSavedGame; /* [HGM] save: ID of game */
248 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
249 extern int chatCount;
250 int chattingPartner;
251
252 /* States for ics_getting_history */
253 #define H_FALSE 0
254 #define H_REQUESTED 1
255 #define H_GOT_REQ_HEADER 2
256 #define H_GOT_UNREQ_HEADER 3
257 #define H_GETTING_MOVES 4
258 #define H_GOT_UNWANTED_HEADER 5
259
260 /* whosays values for GameEnds */
261 #define GE_ICS 0
262 #define GE_ENGINE 1
263 #define GE_PLAYER 2
264 #define GE_FILE 3
265 #define GE_XBOARD 4
266 #define GE_ENGINE1 5
267 #define GE_ENGINE2 6
268
269 /* Maximum number of games in a cmail message */
270 #define CMAIL_MAX_GAMES 20
271
272 /* Different types of move when calling RegisterMove */
273 #define CMAIL_MOVE   0
274 #define CMAIL_RESIGN 1
275 #define CMAIL_DRAW   2
276 #define CMAIL_ACCEPT 3
277
278 /* Different types of result to remember for each game */
279 #define CMAIL_NOT_RESULT 0
280 #define CMAIL_OLD_RESULT 1
281 #define CMAIL_NEW_RESULT 2
282
283 /* Telnet protocol constants */
284 #define TN_WILL 0373
285 #define TN_WONT 0374
286 #define TN_DO   0375
287 #define TN_DONT 0376
288 #define TN_IAC  0377
289 #define TN_ECHO 0001
290 #define TN_SGA  0003
291 #define TN_PORT 23
292
293 /* [AS] */
294 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 {
296     assert( dst != NULL );
297     assert( src != NULL );
298     assert( count > 0 );
299
300     strncpy( dst, src, count );
301     dst[ count-1 ] = '\0';
302     return dst;
303 }
304
305 /* Some compiler can't cast u64 to double
306  * This function do the job for us:
307
308  * We use the highest bit for cast, this only
309  * works if the highest bit is not
310  * in use (This should not happen)
311  *
312  * We used this for all compiler
313  */
314 double
315 u64ToDouble(u64 value)
316 {
317   double r;
318   u64 tmp = value & u64Const(0x7fffffffffffffff);
319   r = (double)(s64)tmp;
320   if (value & u64Const(0x8000000000000000))
321        r +=  9.2233720368547758080e18; /* 2^63 */
322  return r;
323 }
324
325 /* Fake up flags for now, as we aren't keeping track of castling
326    availability yet. [HGM] Change of logic: the flag now only
327    indicates the type of castlings allowed by the rule of the game.
328    The actual rights themselves are maintained in the array
329    castlingRights, as part of the game history, and are not probed
330    by this function.
331  */
332 int
333 PosFlags(index)
334 {
335   int flags = F_ALL_CASTLE_OK;
336   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
337   switch (gameInfo.variant) {
338   case VariantSuicide:
339     flags &= ~F_ALL_CASTLE_OK;
340   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
341     flags |= F_IGNORE_CHECK;
342   case VariantLosers:
343     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
344     break;
345   case VariantAtomic:
346     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347     break;
348   case VariantKriegspiel:
349     flags |= F_KRIEGSPIEL_CAPTURE;
350     break;
351   case VariantCapaRandom:
352   case VariantFischeRandom:
353     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
354   case VariantNoCastle:
355   case VariantShatranj:
356   case VariantCourier:
357     flags &= ~F_ALL_CASTLE_OK;
358     break;
359   default:
360     break;
361   }
362   return flags;
363 }
364
365 FILE *gameFileFP, *debugFP;
366
367 /*
368     [AS] Note: sometimes, the sscanf() function is used to parse the input
369     into a fixed-size buffer. Because of this, we must be prepared to
370     receive strings as long as the size of the input buffer, which is currently
371     set to 4K for Windows and 8K for the rest.
372     So, we must either allocate sufficiently large buffers here, or
373     reduce the size of the input buffer in the input reading part.
374 */
375
376 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
377 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
378 char thinkOutput1[MSG_SIZ*10];
379
380 ChessProgramState first, second;
381
382 /* premove variables */
383 int premoveToX = 0;
384 int premoveToY = 0;
385 int premoveFromX = 0;
386 int premoveFromY = 0;
387 int premovePromoChar = 0;
388 int gotPremove = 0;
389 Boolean alarmSounded;
390 /* end premove variables */
391
392 char *ics_prefix = "$";
393 int ics_type = ICS_GENERIC;
394
395 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
396 int pauseExamForwardMostMove = 0;
397 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
398 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
399 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
400 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
401 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
402 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
403 int whiteFlag = FALSE, blackFlag = FALSE;
404 int userOfferedDraw = FALSE;
405 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
406 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
407 int cmailMoveType[CMAIL_MAX_GAMES];
408 long ics_clock_paused = 0;
409 ProcRef icsPR = NoProc, cmailPR = NoProc;
410 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
411 GameMode gameMode = BeginningOfGame;
412 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
413 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
414 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
415 int hiddenThinkOutputState = 0; /* [AS] */
416 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
417 int adjudicateLossPlies = 6;
418 char white_holding[64], black_holding[64];
419 TimeMark lastNodeCountTime;
420 long lastNodeCount=0;
421 int have_sent_ICS_logon = 0;
422 int movesPerSession;
423 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
424 long timeControl_2; /* [AS] Allow separate time controls */
425 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
426 long timeRemaining[2][MAX_MOVES];
427 int matchGame = 0;
428 TimeMark programStartTime;
429 char ics_handle[MSG_SIZ];
430 int have_set_title = 0;
431
432 /* animateTraining preserves the state of appData.animate
433  * when Training mode is activated. This allows the
434  * response to be animated when appData.animate == TRUE and
435  * appData.animateDragging == TRUE.
436  */
437 Boolean animateTraining;
438
439 GameInfo gameInfo;
440
441 AppData appData;
442
443 Board boards[MAX_MOVES];
444 /* [HGM] Following 7 needed for accurate legality tests: */
445 signed char  epStatus[MAX_MOVES];
446 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
447 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
448 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
451 char  FENepStatus;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0;
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
458     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
459         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
460     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
461         BlackKing, BlackBishop, BlackKnight, BlackRook }
462 };
463
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
465     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
466         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
467     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
468         BlackKing, BlackKing, BlackKnight, BlackRook }
469 };
470
471 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
472     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
473         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
474     { BlackRook, BlackMan, BlackBishop, BlackQueen,
475         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
476 };
477
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
479     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
482         BlackKing, BlackBishop, BlackKnight, BlackRook }
483 };
484
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
486     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
487         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
488     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
489         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
490 };
491
492
493 #if (BOARD_SIZE>=10)
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
495     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
496         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
497     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
498         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
499 };
500
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
503         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
505         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
509     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
512         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
513 };
514
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
516     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
517         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
518     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
519         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
520 };
521
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
523     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
524         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
525     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
526         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
527 };
528
529 #ifdef GOTHIC
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
532         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
534         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
535 };
536 #else // !GOTHIC
537 #define GothicArray CapablancaArray
538 #endif // !GOTHIC
539
540 #ifdef FALCON
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
543         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
545         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
546 };
547 #else // !FALCON
548 #define FalconArray CapablancaArray
549 #endif // !FALCON
550
551 #else // !(BOARD_SIZE>=10)
552 #define XiangqiPosition FIDEArray
553 #define CapablancaArray FIDEArray
554 #define GothicArray FIDEArray
555 #define GreatArray FIDEArray
556 #endif // !(BOARD_SIZE>=10)
557
558 #if (BOARD_SIZE>=12)
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
563         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 };
565 #else // !(BOARD_SIZE>=12)
566 #define CourierArray CapablancaArray
567 #endif // !(BOARD_SIZE>=12)
568
569
570 Board initialPosition;
571
572
573 /* Convert str to a rating. Checks for special cases of "----",
574
575    "++++", etc. Also strips ()'s */
576 int
577 string_to_rating(str)
578   char *str;
579 {
580   while(*str && !isdigit(*str)) ++str;
581   if (!*str)
582     return 0;   /* One of the special "no rating" cases */
583   else
584     return atoi(str);
585 }
586
587 void
588 ClearProgramStats()
589 {
590     /* Init programStats */
591     programStats.movelist[0] = 0;
592     programStats.depth = 0;
593     programStats.nr_moves = 0;
594     programStats.moves_left = 0;
595     programStats.nodes = 0;
596     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
597     programStats.score = 0;
598     programStats.got_only_move = 0;
599     programStats.got_fail = 0;
600     programStats.line_is_book = 0;
601 }
602
603 void
604 InitBackEnd1()
605 {
606     int matched, min, sec;
607
608     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609
610     GetTimeMark(&programStartTime);
611     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
612
613     ClearProgramStats();
614     programStats.ok_to_send = 1;
615     programStats.seen_stat = 0;
616
617     /*
618      * Initialize game list
619      */
620     ListNew(&gameList);
621
622
623     /*
624      * Internet chess server status
625      */
626     if (appData.icsActive) {
627         appData.matchMode = FALSE;
628         appData.matchGames = 0;
629 #if ZIPPY
630         appData.noChessProgram = !appData.zippyPlay;
631 #else
632         appData.zippyPlay = FALSE;
633         appData.zippyTalk = FALSE;
634         appData.noChessProgram = TRUE;
635 #endif
636         if (*appData.icsHelper != NULLCHAR) {
637             appData.useTelnet = TRUE;
638             appData.telnetProgram = appData.icsHelper;
639         }
640     } else {
641         appData.zippyTalk = appData.zippyPlay = FALSE;
642     }
643
644     /* [AS] Initialize pv info list [HGM] and game state */
645     {
646         int i, j;
647
648         for( i=0; i<MAX_MOVES; i++ ) {
649             pvInfoList[i].depth = -1;
650             epStatus[i]=EP_NONE;
651             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
652         }
653     }
654
655     /*
656      * Parse timeControl resource
657      */
658     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
659                           appData.movesPerSession)) {
660         char buf[MSG_SIZ];
661         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
662         DisplayFatalError(buf, 0, 2);
663     }
664
665     /*
666      * Parse searchTime resource
667      */
668     if (*appData.searchTime != NULLCHAR) {
669         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670         if (matched == 1) {
671             searchTime = min * 60;
672         } else if (matched == 2) {
673             searchTime = min * 60 + sec;
674         } else {
675             char buf[MSG_SIZ];
676             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
677             DisplayFatalError(buf, 0, 2);
678         }
679     }
680
681     /* [AS] Adjudication threshold */
682     adjudicateLossThreshold = appData.adjudicateLossThreshold;
683
684     first.which = "first";
685     second.which = "second";
686     first.maybeThinking = second.maybeThinking = FALSE;
687     first.pr = second.pr = NoProc;
688     first.isr = second.isr = NULL;
689     first.sendTime = second.sendTime = 2;
690     first.sendDrawOffers = 1;
691     if (appData.firstPlaysBlack) {
692         first.twoMachinesColor = "black\n";
693         second.twoMachinesColor = "white\n";
694     } else {
695         first.twoMachinesColor = "white\n";
696         second.twoMachinesColor = "black\n";
697     }
698     first.program = appData.firstChessProgram;
699     second.program = appData.secondChessProgram;
700     first.host = appData.firstHost;
701     second.host = appData.secondHost;
702     first.dir = appData.firstDirectory;
703     second.dir = appData.secondDirectory;
704     first.other = &second;
705     second.other = &first;
706     first.initString = appData.initString;
707     second.initString = appData.secondInitString;
708     first.computerString = appData.firstComputerString;
709     second.computerString = appData.secondComputerString;
710     first.useSigint = second.useSigint = TRUE;
711     first.useSigterm = second.useSigterm = TRUE;
712     first.reuse = appData.reuseFirst;
713     second.reuse = appData.reuseSecond;
714     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
715     second.nps = appData.secondNPS;
716     first.useSetboard = second.useSetboard = FALSE;
717     first.useSAN = second.useSAN = FALSE;
718     first.usePing = second.usePing = FALSE;
719     first.lastPing = second.lastPing = 0;
720     first.lastPong = second.lastPong = 0;
721     first.usePlayother = second.usePlayother = FALSE;
722     first.useColors = second.useColors = TRUE;
723     first.useUsermove = second.useUsermove = FALSE;
724     first.sendICS = second.sendICS = FALSE;
725     first.sendName = second.sendName = appData.icsActive;
726     first.sdKludge = second.sdKludge = FALSE;
727     first.stKludge = second.stKludge = FALSE;
728     TidyProgramName(first.program, first.host, first.tidy);
729     TidyProgramName(second.program, second.host, second.tidy);
730     first.matchWins = second.matchWins = 0;
731     strcpy(first.variants, appData.variant);
732     strcpy(second.variants, appData.variant);
733     first.analysisSupport = second.analysisSupport = 2; /* detect */
734     first.analyzing = second.analyzing = FALSE;
735     first.initDone = second.initDone = FALSE;
736
737     /* New features added by Tord: */
738     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
739     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
740     /* End of new features added by Tord. */
741     first.fenOverride  = appData.fenOverride1;
742     second.fenOverride = appData.fenOverride2;
743
744     /* [HGM] time odds: set factor for each machine */
745     first.timeOdds  = appData.firstTimeOdds;
746     second.timeOdds = appData.secondTimeOdds;
747     { int norm = 1;
748         if(appData.timeOddsMode) {
749             norm = first.timeOdds;
750             if(norm > second.timeOdds) norm = second.timeOdds;
751         }
752         first.timeOdds /= norm;
753         second.timeOdds /= norm;
754     }
755
756     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
757     first.accumulateTC = appData.firstAccumulateTC;
758     second.accumulateTC = appData.secondAccumulateTC;
759     first.maxNrOfSessions = second.maxNrOfSessions = 1;
760
761     /* [HGM] debug */
762     first.debug = second.debug = FALSE;
763     first.supportsNPS = second.supportsNPS = UNKNOWN;
764
765     /* [HGM] options */
766     first.optionSettings  = appData.firstOptions;
767     second.optionSettings = appData.secondOptions;
768
769     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
770     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
771     first.isUCI = appData.firstIsUCI; /* [AS] */
772     second.isUCI = appData.secondIsUCI; /* [AS] */
773     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
774     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775
776     if (appData.firstProtocolVersion > PROTOVER ||
777         appData.firstProtocolVersion < 1) {
778       char buf[MSG_SIZ];
779       sprintf(buf, _("protocol version %d not supported"),
780               appData.firstProtocolVersion);
781       DisplayFatalError(buf, 0, 2);
782     } else {
783       first.protocolVersion = appData.firstProtocolVersion;
784     }
785
786     if (appData.secondProtocolVersion > PROTOVER ||
787         appData.secondProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.secondProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       second.protocolVersion = appData.secondProtocolVersion;
794     }
795
796     if (appData.icsActive) {
797         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
798     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
799         appData.clockMode = FALSE;
800         first.sendTime = second.sendTime = 0;
801     }
802
803 #if ZIPPY
804     /* Override some settings from environment variables, for backward
805        compatibility.  Unfortunately it's not feasible to have the env
806        vars just set defaults, at least in xboard.  Ugh.
807     */
808     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
809       ZippyInit();
810     }
811 #endif
812
813     if (appData.noChessProgram) {
814         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815         sprintf(programVersion, "%s", PACKAGE_STRING);
816     } else {
817       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
820     }
821
822     if (!appData.icsActive) {
823       char buf[MSG_SIZ];
824       /* Check for variants that are supported only in ICS mode,
825          or not at all.  Some that are accepted here nevertheless
826          have bugs; see comments below.
827       */
828       VariantClass variant = StringToVariant(appData.variant);
829       switch (variant) {
830       case VariantBughouse:     /* need four players and two boards */
831       case VariantKriegspiel:   /* need to hide pieces and move details */
832       /* case VariantFischeRandom: (Fabien: moved below) */
833         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834         DisplayFatalError(buf, 0, 2);
835         return;
836
837       case VariantUnknown:
838       case VariantLoadable:
839       case Variant29:
840       case Variant30:
841       case Variant31:
842       case Variant32:
843       case Variant33:
844       case Variant34:
845       case Variant35:
846       case Variant36:
847       default:
848         sprintf(buf, _("Unknown variant name %s"), appData.variant);
849         DisplayFatalError(buf, 0, 2);
850         return;
851
852       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
853       case VariantFairy:      /* [HGM] TestLegality definitely off! */
854       case VariantGothic:     /* [HGM] should work */
855       case VariantCapablanca: /* [HGM] should work */
856       case VariantCourier:    /* [HGM] initial forced moves not implemented */
857       case VariantShogi:      /* [HGM] drops not tested for legality */
858       case VariantKnightmate: /* [HGM] should work */
859       case VariantCylinder:   /* [HGM] untested */
860       case VariantFalcon:     /* [HGM] untested */
861       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862                                  offboard interposition not understood */
863       case VariantNormal:     /* definitely works! */
864       case VariantWildCastle: /* pieces not automatically shuffled */
865       case VariantNoCastle:   /* pieces not automatically shuffled */
866       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867       case VariantLosers:     /* should work except for win condition,
868                                  and doesn't know captures are mandatory */
869       case VariantSuicide:    /* should work except for win condition,
870                                  and doesn't know captures are mandatory */
871       case VariantGiveaway:   /* should work except for win condition,
872                                  and doesn't know captures are mandatory */
873       case VariantTwoKings:   /* should work */
874       case VariantAtomic:     /* should work except for win condition */
875       case Variant3Check:     /* should work except for win condition */
876       case VariantShatranj:   /* should work except for all win conditions */
877       case VariantBerolina:   /* might work if TestLegality is off */
878       case VariantCapaRandom: /* should work */
879       case VariantJanus:      /* should work */
880       case VariantSuper:      /* experimental */
881       case VariantGreat:      /* experimental, requires legality testing to be off */
882         break;
883       }
884     }
885
886     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
887     InitEngineUCI( installDir, &second );
888 }
889
890 int NextIntegerFromString( char ** str, long * value )
891 {
892     int result = -1;
893     char * s = *str;
894
895     while( *s == ' ' || *s == '\t' ) {
896         s++;
897     }
898
899     *value = 0;
900
901     if( *s >= '0' && *s <= '9' ) {
902         while( *s >= '0' && *s <= '9' ) {
903             *value = *value * 10 + (*s - '0');
904             s++;
905         }
906
907         result = 0;
908     }
909
910     *str = s;
911
912     return result;
913 }
914
915 int NextTimeControlFromString( char ** str, long * value )
916 {
917     long temp;
918     int result = NextIntegerFromString( str, &temp );
919
920     if( result == 0 ) {
921         *value = temp * 60; /* Minutes */
922         if( **str == ':' ) {
923             (*str)++;
924             result = NextIntegerFromString( str, &temp );
925             *value += temp; /* Seconds */
926         }
927     }
928
929     return result;
930 }
931
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
934     int result = -1; long temp, temp2;
935
936     if(**str != '+') return -1; // old params remain in force!
937     (*str)++;
938     if( NextTimeControlFromString( str, &temp ) ) return -1;
939
940     if(**str != '/') {
941         /* time only: incremental or sudden-death time control */
942         if(**str == '+') { /* increment follows; read it */
943             (*str)++;
944             if(result = NextIntegerFromString( str, &temp2)) return -1;
945             *inc = temp2 * 1000;
946         } else *inc = 0;
947         *moves = 0; *tc = temp * 1000;
948         return 0;
949     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
950
951     (*str)++; /* classical time control */
952     result = NextTimeControlFromString( str, &temp2);
953     if(result == 0) {
954         *moves = temp/60;
955         *tc    = temp2 * 1000;
956         *inc   = 0;
957     }
958     return result;
959 }
960
961 int GetTimeQuota(int movenr)
962 {   /* [HGM] get time to add from the multi-session time-control string */
963     int moves=1; /* kludge to force reading of first session */
964     long time, increment;
965     char *s = fullTimeControlString;
966
967     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968     do {
969         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971         if(movenr == -1) return time;    /* last move before new session     */
972         if(!moves) return increment;     /* current session is incremental   */
973         if(movenr >= 0) movenr -= moves; /* we already finished this session */
974     } while(movenr >= -1);               /* try again for next session       */
975
976     return 0; // no new time quota on this move
977 }
978
979 int
980 ParseTimeControl(tc, ti, mps)
981      char *tc;
982      int ti;
983      int mps;
984 {
985   long tc1;
986   long tc2;
987   char buf[MSG_SIZ];
988   
989   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
990   if(ti > 0) {
991     if(mps)
992       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993     else sprintf(buf, "+%s+%d", tc, ti);
994   } else {
995     if(mps)
996              sprintf(buf, "+%d/%s", mps, tc);
997     else sprintf(buf, "+%s", tc);
998   }
999   fullTimeControlString = StrSave(buf);
1000   
1001   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1002     return FALSE;
1003   }
1004   
1005   if( *tc == '/' ) {
1006     /* Parse second time control */
1007     tc++;
1008     
1009     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1010       return FALSE;
1011     }
1012     
1013     if( tc2 == 0 ) {
1014       return FALSE;
1015     }
1016     
1017     timeControl_2 = tc2 * 1000;
1018   }
1019   else {
1020     timeControl_2 = 0;
1021   }
1022   
1023   if( tc1 == 0 ) {
1024     return FALSE;
1025   }
1026   
1027   timeControl = tc1 * 1000;
1028   
1029   if (ti >= 0) {
1030     timeIncrement = ti * 1000;  /* convert to ms */
1031     movesPerSession = 0;
1032   } else {
1033     timeIncrement = 0;
1034     movesPerSession = mps;
1035   }
1036   return TRUE;
1037 }
1038
1039 void
1040 InitBackEnd2()
1041 {
1042   if (appData.debugMode) {
1043     fprintf(debugFP, "%s\n", programVersion);
1044   }
1045
1046   if (appData.matchGames > 0) {
1047     appData.matchMode = TRUE;
1048   } else if (appData.matchMode) {
1049     appData.matchGames = 1;
1050   }
1051   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052     appData.matchGames = appData.sameColorGames;
1053   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056   }
1057   Reset(TRUE, FALSE);
1058   if (appData.noChessProgram || first.protocolVersion == 1) {
1059     InitBackEnd3();
1060   } else {
1061     /* kludge: allow timeout for initial "feature" commands */
1062     FreezeUI();
1063     DisplayMessage("", _("Starting chess program"));
1064     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065   }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate();
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile;
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway,
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '('
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr")
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        return;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2014    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2015    
2016    DrawPosition(TRUE, boards[currentMove]);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static int player1Rating = -1;
2027 static int player2Rating = -1;
2028 /*----------------------------*/
2029
2030 ColorClass curColor = ColorNormal;
2031 int suppressKibitz = 0;
2032
2033 void
2034 read_from_ics(isr, closure, data, count, error)
2035      InputSourceRef isr;
2036      VOIDSTAR closure;
2037      char *data;
2038      int count;
2039      int error;
2040 {
2041 #define BUF_SIZE 8192
2042 #define STARTED_NONE 0
2043 #define STARTED_MOVES 1
2044 #define STARTED_BOARD 2
2045 #define STARTED_OBSERVE 3
2046 #define STARTED_HOLDINGS 4
2047 #define STARTED_CHATTER 5
2048 #define STARTED_COMMENT 6
2049 #define STARTED_MOVES_NOHIDE 7
2050
2051     static int started = STARTED_NONE;
2052     static char parse[20000];
2053     static int parse_pos = 0;
2054     static char buf[BUF_SIZE + 1];
2055     static int firstTime = TRUE, intfSet = FALSE;
2056     static ColorClass prevColor = ColorNormal;
2057     static int savingComment = FALSE;
2058     char str[500];
2059     int i, oldi;
2060     int buf_len;
2061     int next_out;
2062     int tkind;
2063     int backup;    /* [DM] For zippy color lines */
2064     char *p;
2065     char talker[MSG_SIZ]; // [HGM] chat
2066     int channel;
2067
2068     if (appData.debugMode) {
2069       if (!error) {
2070         fprintf(debugFP, "<ICS: ");
2071         show_bytes(debugFP, data, count);
2072         fprintf(debugFP, "\n");
2073       }
2074     }
2075
2076     if (appData.debugMode) { int f = forwardMostMove;
2077         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2078                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2079     }
2080     if (count > 0) {
2081         /* If last read ended with a partial line that we couldn't parse,
2082            prepend it to the new read and try again. */
2083         if (leftover_len > 0) {
2084             for (i=0; i<leftover_len; i++)
2085               buf[i] = buf[leftover_start + i];
2086         }
2087
2088         /* Copy in new characters, removing nulls and \r's */
2089         buf_len = leftover_len;
2090         for (i = 0; i < count; i++) {
2091             if (data[i] != NULLCHAR && data[i] != '\r')
2092               buf[buf_len++] = data[i];
2093             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2094                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2095                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2096                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2097                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2098             }
2099         }
2100
2101         buf[buf_len] = NULLCHAR;
2102         next_out = leftover_len;
2103         leftover_start = 0;
2104
2105         i = 0;
2106         while (i < buf_len) {
2107             /* Deal with part of the TELNET option negotiation
2108                protocol.  We refuse to do anything beyond the
2109                defaults, except that we allow the WILL ECHO option,
2110                which ICS uses to turn off password echoing when we are
2111                directly connected to it.  We reject this option
2112                if localLineEditing mode is on (always on in xboard)
2113                and we are talking to port 23, which might be a real
2114                telnet server that will try to keep WILL ECHO on permanently.
2115              */
2116             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2117                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2118                 unsigned char option;
2119                 oldi = i;
2120                 switch ((unsigned char) buf[++i]) {
2121                   case TN_WILL:
2122                     if (appData.debugMode)
2123                       fprintf(debugFP, "\n<WILL ");
2124                     switch (option = (unsigned char) buf[++i]) {
2125                       case TN_ECHO:
2126                         if (appData.debugMode)
2127                           fprintf(debugFP, "ECHO ");
2128                         /* Reply only if this is a change, according
2129                            to the protocol rules. */
2130                         if (remoteEchoOption) break;
2131                         if (appData.localLineEditing &&
2132                             atoi(appData.icsPort) == TN_PORT) {
2133                             TelnetRequest(TN_DONT, TN_ECHO);
2134                         } else {
2135                             EchoOff();
2136                             TelnetRequest(TN_DO, TN_ECHO);
2137                             remoteEchoOption = TRUE;
2138                         }
2139                         break;
2140                       default:
2141                         if (appData.debugMode)
2142                           fprintf(debugFP, "%d ", option);
2143                         /* Whatever this is, we don't want it. */
2144                         TelnetRequest(TN_DONT, option);
2145                         break;
2146                     }
2147                     break;
2148                   case TN_WONT:
2149                     if (appData.debugMode)
2150                       fprintf(debugFP, "\n<WONT ");
2151                     switch (option = (unsigned char) buf[++i]) {
2152                       case TN_ECHO:
2153                         if (appData.debugMode)
2154                           fprintf(debugFP, "ECHO ");
2155                         /* Reply only if this is a change, according
2156                            to the protocol rules. */
2157                         if (!remoteEchoOption) break;
2158                         EchoOn();
2159                         TelnetRequest(TN_DONT, TN_ECHO);
2160                         remoteEchoOption = FALSE;
2161                         break;
2162                       default:
2163                         if (appData.debugMode)
2164                           fprintf(debugFP, "%d ", (unsigned char) option);
2165                         /* Whatever this is, it must already be turned
2166                            off, because we never agree to turn on
2167                            anything non-default, so according to the
2168                            protocol rules, we don't reply. */
2169                         break;
2170                     }
2171                     break;
2172                   case TN_DO:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<DO ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       default:
2177                         /* Whatever this is, we refuse to do it. */
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", option);
2180                         TelnetRequest(TN_WONT, option);
2181                         break;
2182                     }
2183                     break;
2184                   case TN_DONT:
2185                     if (appData.debugMode)
2186                       fprintf(debugFP, "\n<DONT ");
2187                     switch (option = (unsigned char) buf[++i]) {
2188                       default:
2189                         if (appData.debugMode)
2190                           fprintf(debugFP, "%d ", option);
2191                         /* Whatever this is, we are already not doing
2192                            it, because we never agree to do anything
2193                            non-default, so according to the protocol
2194                            rules, we don't reply. */
2195                         break;
2196                     }
2197                     break;
2198                   case TN_IAC:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<IAC ");
2201                     /* Doubled IAC; pass it through */
2202                     i--;
2203                     break;
2204                   default:
2205                     if (appData.debugMode)
2206                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2207                     /* Drop all other telnet commands on the floor */
2208                     break;
2209                 }
2210                 if (oldi > next_out)
2211                   SendToPlayer(&buf[next_out], oldi - next_out);
2212                 if (++i > next_out)
2213                   next_out = i;
2214                 continue;
2215             }
2216
2217             /* OK, this at least will *usually* work */
2218             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2219                 loggedOn = TRUE;
2220             }
2221
2222             if (loggedOn && !intfSet) {
2223                 if (ics_type == ICS_ICC) {
2224                   sprintf(str,
2225                           "/set-quietly interface %s\n/set-quietly style 12\n",
2226                           programVersion);
2227           if (!appData.noJoin)
2228               strcat(str, "/set-quietly wrap 0\n");
2229                 } else if (ics_type == ICS_CHESSNET) {
2230                   sprintf(str, "/style 12\n");
2231                 } else {
2232                   strcpy(str, "alias $ @\n$set interface ");
2233                   strcat(str, programVersion);
2234                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2235 #ifdef WIN32
2236                   strcat(str, "$iset nohighlight 1\n");
2237 #endif
2238           if (!appData.noJoin)
2239               strcat(str, "$iset nowrap 1\n");
2240                   strcat(str, "$iset lock 1\n$style 12\n");
2241                 }
2242                 SendToICS(str);
2243                 NotifyFrontendLogin();
2244                 intfSet = TRUE;
2245             }
2246
2247             if (started == STARTED_COMMENT) {
2248                 /* Accumulate characters in comment */
2249                 parse[parse_pos++] = buf[i];
2250                 if (buf[i] == '\n') {
2251                     parse[parse_pos] = NULLCHAR;
2252                     if(chattingPartner>=0) {
2253                         char mess[MSG_SIZ];
2254                         sprintf(mess, "%s%s", talker, parse);
2255                         OutputChatMessage(chattingPartner, mess);
2256                         chattingPartner = -1;
2257                     } else
2258                     if(!suppressKibitz) // [HGM] kibitz
2259                         AppendComment(forwardMostMove, StripHighlight(parse));
2260                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2261                         int nrDigit = 0, nrAlph = 0, i;
2262                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2263                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2264                         parse[parse_pos] = NULLCHAR;
2265                         // try to be smart: if it does not look like search info, it should go to
2266                         // ICS interaction window after all, not to engine-output window.
2267                         for(i=0; i<parse_pos; i++) { // count letters and digits
2268                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2269                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2270                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2271                         }
2272                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2273                             int depth=0; float score;
2274                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2275                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2276                                 pvInfoList[forwardMostMove-1].depth = depth;
2277                                 pvInfoList[forwardMostMove-1].score = 100*score;
2278                             }
2279                             OutputKibitz(suppressKibitz, parse);
2280                         } else {
2281                             char tmp[MSG_SIZ];
2282                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2283                             SendToPlayer(tmp, strlen(tmp));
2284                         }
2285                     }
2286                     started = STARTED_NONE;
2287                 } else {
2288                     /* Don't match patterns against characters in chatter */
2289                     i++;
2290                     continue;
2291                 }
2292             }
2293             if (started == STARTED_CHATTER) {
2294                 if (buf[i] != '\n') {
2295                     /* Don't match patterns against characters in chatter */
2296                     i++;
2297                     continue;
2298                 }
2299                 started = STARTED_NONE;
2300             }
2301
2302             /* Kludge to deal with rcmd protocol */
2303             if (firstTime && looking_at(buf, &i, "\001*")) {
2304                 DisplayFatalError(&buf[1], 0, 1);
2305                 continue;
2306             } else {
2307                 firstTime = FALSE;
2308             }
2309
2310             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2311                 ics_type = ICS_ICC;
2312                 ics_prefix = "/";
2313                 if (appData.debugMode)
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);
2315                 continue;
2316             }
2317             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2318                 ics_type = ICS_FICS;
2319                 ics_prefix = "$";
2320                 if (appData.debugMode)
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);
2322                 continue;
2323             }
2324             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2325                 ics_type = ICS_CHESSNET;
2326                 ics_prefix = "/";
2327                 if (appData.debugMode)
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);
2329                 continue;
2330             }
2331
2332             if (!loggedOn &&
2333                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2334                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2335                  looking_at(buf, &i, "will be \"*\""))) {
2336               strcpy(ics_handle, star_match[0]);
2337               continue;
2338             }
2339
2340             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2341               char buf[MSG_SIZ];
2342               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2343               DisplayIcsInteractionTitle(buf);
2344               have_set_title = TRUE;
2345             }
2346
2347             /* skip finger notes */
2348             if (started == STARTED_NONE &&
2349                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2350                  (buf[i] == '1' && buf[i+1] == '0')) &&
2351                 buf[i+2] == ':' && buf[i+3] == ' ') {
2352               started = STARTED_CHATTER;
2353               i += 3;
2354               continue;
2355             }
2356
2357             /* skip formula vars */
2358             if (started == STARTED_NONE &&
2359                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2360               started = STARTED_CHATTER;
2361               i += 3;
2362               continue;
2363             }
2364
2365             oldi = i;
2366             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2367             if (appData.autoKibitz && started == STARTED_NONE &&
2368                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2369                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2370                 if(looking_at(buf, &i, "* kibitzes: ") &&
2371                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2372                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2373                         suppressKibitz = TRUE;
2374                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2375                                 && (gameMode == IcsPlayingWhite)) ||
2376                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2377                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2378                             started = STARTED_CHATTER; // own kibitz we simply discard
2379                         else {
2380                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2381                             parse_pos = 0; parse[0] = NULLCHAR;
2382                             savingComment = TRUE;
2383                             suppressKibitz = gameMode != IcsObserving ? 2 :
2384                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2385                         }
2386                         continue;
2387                 } else
2388                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2389                     started = STARTED_CHATTER;
2390                     suppressKibitz = TRUE;
2391                 }
2392             } // [HGM] kibitz: end of patch
2393
2394 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2395
2396             // [HGM] chat: intercept tells by users for which we have an open chat window
2397             channel = -1;
2398             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2399                                            looking_at(buf, &i, "* whispers:") ||
2400                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2401                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2402                 int p;
2403                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2404                 chattingPartner = -1;
2405
2406                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2407                 for(p=0; p<MAX_CHAT; p++) {
2408                     if(channel == atoi(chatPartner[p])) {
2409                     talker[0] = '['; strcat(talker, "]");
2410                     chattingPartner = p; break;
2411                     }
2412                 } else
2413                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2414                 for(p=0; p<MAX_CHAT; p++) {
2415                     if(!strcmp("WHISPER", chatPartner[p])) {
2416                         talker[0] = '['; strcat(talker, "]");
2417                         chattingPartner = p; break;
2418                     }
2419                 }
2420                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2421                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2422                     talker[0] = 0;
2423                     chattingPartner = p; break;
2424                 }
2425                 if(chattingPartner<0) i = oldi; else {
2426                     started = STARTED_COMMENT;
2427                     parse_pos = 0; parse[0] = NULLCHAR;
2428                     savingComment = TRUE;
2429                     suppressKibitz = TRUE;
2430                 }
2431             } // [HGM] chat: end of patch
2432
2433             if (appData.zippyTalk || appData.zippyPlay) {
2434                 /* [DM] Backup address for color zippy lines */
2435                 backup = i;
2436 #if ZIPPY
2437        #ifdef WIN32
2438                if (loggedOn == TRUE)
2439                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2440                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2441        #else
2442                 if (ZippyControl(buf, &i) ||
2443                     ZippyConverse(buf, &i) ||
2444                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2445                       loggedOn = TRUE;
2446                       if (!appData.colorize) continue;
2447                 }
2448        #endif
2449 #endif
2450             } // [DM] 'else { ' deleted
2451                 if (
2452                     /* Regular tells and says */
2453                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2454                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2455                     looking_at(buf, &i, "* says: ") ||
2456                     /* Don't color "message" or "messages" output */
2457                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2458                     looking_at(buf, &i, "*. * at *:*: ") ||
2459                     looking_at(buf, &i, "--* (*:*): ") ||
2460                     /* Message notifications (same color as tells) */
2461                     looking_at(buf, &i, "* has left a message ") ||
2462                     looking_at(buf, &i, "* just sent you a message:\n") ||
2463                     /* Whispers and kibitzes */
2464                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2465                     looking_at(buf, &i, "* kibitzes: ") ||
2466                     /* Channel tells */
2467                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2468
2469                   if (tkind == 1 && strchr(star_match[0], ':')) {
2470                       /* Avoid "tells you:" spoofs in channels */
2471                      tkind = 3;
2472                   }
2473                   if (star_match[0][0] == NULLCHAR ||
2474                       strchr(star_match[0], ' ') ||
2475                       (tkind == 3 && strchr(star_match[1], ' '))) {
2476                     /* Reject bogus matches */
2477                     i = oldi;
2478                   } else {
2479                     if (appData.colorize) {
2480                       if (oldi > next_out) {
2481                         SendToPlayer(&buf[next_out], oldi - next_out);
2482                         next_out = oldi;
2483                       }
2484                       switch (tkind) {
2485                       case 1:
2486                         Colorize(ColorTell, FALSE);
2487                         curColor = ColorTell;
2488                         break;
2489                       case 2:
2490                         Colorize(ColorKibitz, FALSE);
2491                         curColor = ColorKibitz;
2492                         break;
2493                       case 3:
2494                         p = strrchr(star_match[1], '(');
2495                         if (p == NULL) {
2496                           p = star_match[1];
2497                         } else {
2498                           p++;
2499                         }
2500                         if (atoi(p) == 1) {
2501                           Colorize(ColorChannel1, FALSE);
2502                           curColor = ColorChannel1;
2503                         } else {
2504                           Colorize(ColorChannel, FALSE);
2505                           curColor = ColorChannel;
2506                         }
2507                         break;
2508                       case 5:
2509                         curColor = ColorNormal;
2510                         break;
2511                       }
2512                     }
2513                     if (started == STARTED_NONE && appData.autoComment &&
2514                         (gameMode == IcsObserving ||
2515                          gameMode == IcsPlayingWhite ||
2516                          gameMode == IcsPlayingBlack)) {
2517                       parse_pos = i - oldi;
2518                       memcpy(parse, &buf[oldi], parse_pos);
2519                       parse[parse_pos] = NULLCHAR;
2520                       started = STARTED_COMMENT;
2521                       savingComment = TRUE;
2522                     } else {
2523                       started = STARTED_CHATTER;
2524                       savingComment = FALSE;
2525                     }
2526                     loggedOn = TRUE;
2527                     continue;
2528                   }
2529                 }
2530
2531                 if (looking_at(buf, &i, "* s-shouts: ") ||
2532                     looking_at(buf, &i, "* c-shouts: ")) {
2533                     if (appData.colorize) {
2534                         if (oldi > next_out) {
2535                             SendToPlayer(&buf[next_out], oldi - next_out);
2536                             next_out = oldi;
2537                         }
2538                         Colorize(ColorSShout, FALSE);
2539                         curColor = ColorSShout;
2540                     }
2541                     loggedOn = TRUE;
2542                     started = STARTED_CHATTER;
2543                     continue;
2544                 }
2545
2546                 if (looking_at(buf, &i, "--->")) {
2547                     loggedOn = TRUE;
2548                     continue;
2549                 }
2550
2551                 if (looking_at(buf, &i, "* shouts: ") ||
2552                     looking_at(buf, &i, "--> ")) {
2553                     if (appData.colorize) {
2554                         if (oldi > next_out) {
2555                             SendToPlayer(&buf[next_out], oldi - next_out);
2556                             next_out = oldi;
2557                         }
2558                         Colorize(ColorShout, FALSE);
2559                         curColor = ColorShout;
2560                     }
2561                     loggedOn = TRUE;
2562                     started = STARTED_CHATTER;
2563                     continue;
2564                 }
2565
2566                 if (looking_at( buf, &i, "Challenge:")) {
2567                     if (appData.colorize) {
2568                         if (oldi > next_out) {
2569                             SendToPlayer(&buf[next_out], oldi - next_out);
2570                             next_out = oldi;
2571                         }
2572                         Colorize(ColorChallenge, FALSE);
2573                         curColor = ColorChallenge;
2574                     }
2575                     loggedOn = TRUE;
2576                     continue;
2577                 }
2578
2579                 if (looking_at(buf, &i, "* offers you") ||
2580                     looking_at(buf, &i, "* offers to be") ||
2581                     looking_at(buf, &i, "* would like to") ||
2582                     looking_at(buf, &i, "* requests to") ||
2583                     looking_at(buf, &i, "Your opponent offers") ||
2584                     looking_at(buf, &i, "Your opponent requests")) {
2585
2586                     if (appData.colorize) {
2587                         if (oldi > next_out) {
2588                             SendToPlayer(&buf[next_out], oldi - next_out);
2589                             next_out = oldi;
2590                         }
2591                         Colorize(ColorRequest, FALSE);
2592                         curColor = ColorRequest;
2593                     }
2594                     continue;
2595                 }
2596
2597                 if (looking_at(buf, &i, "* (*) seeking")) {
2598                     if (appData.colorize) {
2599                         if (oldi > next_out) {
2600                             SendToPlayer(&buf[next_out], oldi - next_out);
2601                             next_out = oldi;
2602                         }
2603                         Colorize(ColorSeek, FALSE);
2604                         curColor = ColorSeek;
2605                     }
2606                     continue;
2607             }
2608
2609             if (looking_at(buf, &i, "\\   ")) {
2610                 if (prevColor != ColorNormal) {
2611                     if (oldi > next_out) {
2612                         SendToPlayer(&buf[next_out], oldi - next_out);
2613                         next_out = oldi;
2614                     }
2615                     Colorize(prevColor, TRUE);
2616                     curColor = prevColor;
2617                 }
2618                 if (savingComment) {
2619                     parse_pos = i - oldi;
2620                     memcpy(parse, &buf[oldi], parse_pos);
2621                     parse[parse_pos] = NULLCHAR;
2622                     started = STARTED_COMMENT;
2623                 } else {
2624                     started = STARTED_CHATTER;
2625                 }
2626                 continue;
2627             }
2628
2629             if (looking_at(buf, &i, "Black Strength :") ||
2630                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2631                 looking_at(buf, &i, "<10>") ||
2632                 looking_at(buf, &i, "#@#")) {
2633                 /* Wrong board style */
2634                 loggedOn = TRUE;
2635                 SendToICS(ics_prefix);
2636                 SendToICS("set style 12\n");
2637                 SendToICS(ics_prefix);
2638                 SendToICS("refresh\n");
2639                 continue;
2640             }
2641
2642             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2643                 ICSInitScript();
2644                 have_sent_ICS_logon = 1;
2645                 continue;
2646             }
2647
2648             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2649                 (looking_at(buf, &i, "\n<12> ") ||
2650                  looking_at(buf, &i, "<12> "))) {
2651                 loggedOn = TRUE;
2652                 if (oldi > next_out) {
2653                     SendToPlayer(&buf[next_out], oldi - next_out);
2654                 }
2655                 next_out = i;
2656                 started = STARTED_BOARD;
2657                 parse_pos = 0;
2658                 continue;
2659             }
2660
2661             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2662                 looking_at(buf, &i, "<b1> ")) {
2663                 if (oldi > next_out) {
2664                     SendToPlayer(&buf[next_out], oldi - next_out);
2665                 }
2666                 next_out = i;
2667                 started = STARTED_HOLDINGS;
2668                 parse_pos = 0;
2669                 continue;
2670             }
2671
2672             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2673                 loggedOn = TRUE;
2674                 /* Header for a move list -- first line */
2675
2676                 switch (ics_getting_history) {
2677                   case H_FALSE:
2678                     switch (gameMode) {
2679                       case IcsIdle:
2680                       case BeginningOfGame:
2681                         /* User typed "moves" or "oldmoves" while we
2682                            were idle.  Pretend we asked for these
2683                            moves and soak them up so user can step
2684                            through them and/or save them.
2685                            */
2686                         Reset(FALSE, TRUE);
2687                         gameMode = IcsObserving;
2688                         ModeHighlight();
2689                         ics_gamenum = -1;
2690                         ics_getting_history = H_GOT_UNREQ_HEADER;
2691                         break;
2692                       case EditGame: /*?*/
2693                       case EditPosition: /*?*/
2694                         /* Should above feature work in these modes too? */
2695                         /* For now it doesn't */
2696                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2697                         break;
2698                       default:
2699                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2700                         break;
2701                     }
2702                     break;
2703                   case H_REQUESTED:
2704                     /* Is this the right one? */
2705                     if (gameInfo.white && gameInfo.black &&
2706                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2707                         strcmp(gameInfo.black, star_match[2]) == 0) {
2708                         /* All is well */
2709                         ics_getting_history = H_GOT_REQ_HEADER;
2710                     }
2711                     break;
2712                   case H_GOT_REQ_HEADER:
2713                   case H_GOT_UNREQ_HEADER:
2714                   case H_GOT_UNWANTED_HEADER:
2715                   case H_GETTING_MOVES:
2716                     /* Should not happen */
2717                     DisplayError(_("Error gathering move list: two headers"), 0);
2718                     ics_getting_history = H_FALSE;
2719                     break;
2720                 }
2721
2722                 /* Save player ratings into gameInfo if needed */
2723                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2724                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2725                     (gameInfo.whiteRating == -1 ||
2726                      gameInfo.blackRating == -1)) {
2727
2728                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2729                     gameInfo.blackRating = string_to_rating(star_match[3]);
2730                     if (appData.debugMode)
2731                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2732                               gameInfo.whiteRating, gameInfo.blackRating);
2733                 }
2734                 continue;
2735             }
2736
2737             if (looking_at(buf, &i,
2738               "* * match, initial time: * minute*, increment: * second")) {
2739                 /* Header for a move list -- second line */
2740                 /* Initial board will follow if this is a wild game */
2741                 if (gameInfo.event != NULL) free(gameInfo.event);
2742                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2743                 gameInfo.event = StrSave(str);
2744                 /* [HGM] we switched variant. Translate boards if needed. */
2745                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2746                 continue;
2747             }
2748
2749             if (looking_at(buf, &i, "Move  ")) {
2750                 /* Beginning of a move list */
2751                 switch (ics_getting_history) {
2752                   case H_FALSE:
2753                     /* Normally should not happen */
2754                     /* Maybe user hit reset while we were parsing */
2755                     break;
2756                   case H_REQUESTED:
2757                     /* Happens if we are ignoring a move list that is not
2758                      * the one we just requested.  Common if the user
2759                      * tries to observe two games without turning off
2760                      * getMoveList */
2761                     break;
2762                   case H_GETTING_MOVES:
2763                     /* Should not happen */
2764                     DisplayError(_("Error gathering move list: nested"), 0);
2765                     ics_getting_history = H_FALSE;
2766                     break;
2767                   case H_GOT_REQ_HEADER:
2768                     ics_getting_history = H_GETTING_MOVES;
2769                     started = STARTED_MOVES;
2770                     parse_pos = 0;
2771                     if (oldi > next_out) {
2772                         SendToPlayer(&buf[next_out], oldi - next_out);
2773                     }
2774                     break;
2775                   case H_GOT_UNREQ_HEADER:
2776                     ics_getting_history = H_GETTING_MOVES;
2777                     started = STARTED_MOVES_NOHIDE;
2778                     parse_pos = 0;
2779                     break;
2780                   case H_GOT_UNWANTED_HEADER:
2781                     ics_getting_history = H_FALSE;
2782                     break;
2783                 }
2784                 continue;
2785             }
2786
2787             if (looking_at(buf, &i, "% ") ||
2788                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2789                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2790                 savingComment = FALSE;
2791                 switch (started) {
2792                   case STARTED_MOVES:
2793                   case STARTED_MOVES_NOHIDE:
2794                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2795                     parse[parse_pos + i - oldi] = NULLCHAR;
2796                     ParseGameHistory(parse);
2797 #if ZIPPY
2798                     if (appData.zippyPlay && first.initDone) {
2799                         FeedMovesToProgram(&first, forwardMostMove);
2800                         if (gameMode == IcsPlayingWhite) {
2801                             if (WhiteOnMove(forwardMostMove)) {
2802                                 if (first.sendTime) {
2803                                   if (first.useColors) {
2804                                     SendToProgram("black\n", &first);
2805                                   }
2806                                   SendTimeRemaining(&first, TRUE);
2807                                 }
2808                                 if (first.useColors) {
2809                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2810                                 }
2811                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2812                                 first.maybeThinking = TRUE;
2813                             } else {
2814                                 if (first.usePlayother) {
2815                                   if (first.sendTime) {
2816                                     SendTimeRemaining(&first, TRUE);
2817                                   }
2818                                   SendToProgram("playother\n", &first);
2819                                   firstMove = FALSE;
2820                                 } else {
2821                                   firstMove = TRUE;
2822                                 }
2823                             }
2824                         } else if (gameMode == IcsPlayingBlack) {
2825                             if (!WhiteOnMove(forwardMostMove)) {
2826                                 if (first.sendTime) {
2827                                   if (first.useColors) {
2828                                     SendToProgram("white\n", &first);
2829                                   }
2830                                   SendTimeRemaining(&first, FALSE);
2831                                 }
2832                                 if (first.useColors) {
2833                                   SendToProgram("black\n", &first);
2834                                 }
2835                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2836                                 first.maybeThinking = TRUE;
2837                             } else {
2838                                 if (first.usePlayother) {
2839                                   if (first.sendTime) {
2840                                     SendTimeRemaining(&first, FALSE);
2841                                   }
2842                                   SendToProgram("playother\n", &first);
2843                                   firstMove = FALSE;
2844                                 } else {
2845                                   firstMove = TRUE;
2846                                 }
2847                             }
2848                         }
2849                     }
2850 #endif
2851                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2852                         /* Moves came from oldmoves or moves command
2853                            while we weren't doing anything else.
2854                            */
2855                         currentMove = forwardMostMove;
2856                         ClearHighlights();/*!!could figure this out*/
2857                         flipView = appData.flipView;
2858                         DrawPosition(FALSE, boards[currentMove]);
2859                         DisplayBothClocks();
2860                         sprintf(str, "%s vs. %s",
2861                                 gameInfo.white, gameInfo.black);
2862                         DisplayTitle(str);
2863                         gameMode = IcsIdle;
2864                     } else {
2865                         /* Moves were history of an active game */
2866                         if (gameInfo.resultDetails != NULL) {
2867                             free(gameInfo.resultDetails);
2868                             gameInfo.resultDetails = NULL;
2869                         }
2870                     }
2871                     HistorySet(parseList, backwardMostMove,
2872                                forwardMostMove, currentMove-1);
2873                     DisplayMove(currentMove - 1);
2874                     if (started == STARTED_MOVES) next_out = i;
2875                     started = STARTED_NONE;
2876                     ics_getting_history = H_FALSE;
2877                     break;
2878
2879                   case STARTED_OBSERVE:
2880                     started = STARTED_NONE;
2881                     SendToICS(ics_prefix);
2882                     SendToICS("refresh\n");
2883                     break;
2884
2885                   default:
2886                     break;
2887                 }
2888                 if(bookHit) { // [HGM] book: simulate book reply
2889                     static char bookMove[MSG_SIZ]; // a bit generous?
2890
2891                     programStats.nodes = programStats.depth = programStats.time =
2892                     programStats.score = programStats.got_only_move = 0;
2893                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2894
2895                     strcpy(bookMove, "move ");
2896                     strcat(bookMove, bookHit);
2897                     HandleMachineMove(bookMove, &first);
2898                 }
2899                 continue;
2900             }
2901
2902             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2903                  started == STARTED_HOLDINGS ||
2904                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2905                 /* Accumulate characters in move list or board */
2906                 parse[parse_pos++] = buf[i];
2907             }
2908
2909             /* Start of game messages.  Mostly we detect start of game
2910                when the first board image arrives.  On some versions
2911                of the ICS, though, we need to do a "refresh" after starting
2912                to observe in order to get the current board right away. */
2913             if (looking_at(buf, &i, "Adding game * to observation list")) {
2914                 started = STARTED_OBSERVE;
2915                 continue;
2916             }
2917
2918             /* Handle auto-observe */
2919             if (appData.autoObserve &&
2920                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2921                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2922                 char *player;
2923                 /* Choose the player that was highlighted, if any. */
2924                 if (star_match[0][0] == '\033' ||
2925                     star_match[1][0] != '\033') {
2926                     player = star_match[0];
2927                 } else {
2928                     player = star_match[2];
2929                 }
2930                 sprintf(str, "%sobserve %s\n",
2931                         ics_prefix, StripHighlightAndTitle(player));
2932                 SendToICS(str);
2933
2934                 /* Save ratings from notify string */
2935                 strcpy(player1Name, star_match[0]);
2936                 player1Rating = string_to_rating(star_match[1]);
2937                 strcpy(player2Name, star_match[2]);
2938                 player2Rating = string_to_rating(star_match[3]);
2939
2940                 if (appData.debugMode)
2941                   fprintf(debugFP,
2942                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2943                           player1Name, player1Rating,
2944                           player2Name, player2Rating);
2945
2946                 continue;
2947             }
2948
2949             /* Deal with automatic examine mode after a game,
2950                and with IcsObserving -> IcsExamining transition */
2951             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2952                 looking_at(buf, &i, "has made you an examiner of game *")) {
2953
2954                 int gamenum = atoi(star_match[0]);
2955                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2956                     gamenum == ics_gamenum) {
2957                     /* We were already playing or observing this game;
2958                        no need to refetch history */
2959                     gameMode = IcsExamining;
2960                     if (pausing) {
2961                         pauseExamForwardMostMove = forwardMostMove;
2962                     } else if (currentMove < forwardMostMove) {
2963                         ForwardInner(forwardMostMove);
2964                     }
2965                 } else {
2966                     /* I don't think this case really can happen */
2967                     SendToICS(ics_prefix);
2968                     SendToICS("refresh\n");
2969                 }
2970                 continue;
2971             }
2972
2973             /* Error messages */
2974 //          if (ics_user_moved) {
2975             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2976                 if (looking_at(buf, &i, "Illegal move") ||
2977                     looking_at(buf, &i, "Not a legal move") ||
2978                     looking_at(buf, &i, "Your king is in check") ||
2979                     looking_at(buf, &i, "It isn't your turn") ||
2980                     looking_at(buf, &i, "It is not your move")) {
2981                     /* Illegal move */
2982                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2983                         currentMove = --forwardMostMove;
2984                         DisplayMove(currentMove - 1); /* before DMError */
2985                         DrawPosition(FALSE, boards[currentMove]);
2986                         SwitchClocks();
2987                         DisplayBothClocks();
2988                     }
2989                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2990                     ics_user_moved = 0;
2991                     continue;
2992                 }
2993             }
2994
2995             if (looking_at(buf, &i, "still have time") ||
2996                 looking_at(buf, &i, "not out of time") ||
2997                 looking_at(buf, &i, "either player is out of time") ||
2998                 looking_at(buf, &i, "has timeseal; checking")) {
2999                 /* We must have called his flag a little too soon */
3000                 whiteFlag = blackFlag = FALSE;
3001                 continue;
3002             }
3003
3004             if (looking_at(buf, &i, "added * seconds to") ||
3005                 looking_at(buf, &i, "seconds were added to")) {
3006                 /* Update the clocks */
3007                 SendToICS(ics_prefix);
3008                 SendToICS("refresh\n");
3009                 continue;
3010             }
3011
3012             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3013                 ics_clock_paused = TRUE;
3014                 StopClocks();
3015                 continue;
3016             }
3017
3018             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3019                 ics_clock_paused = FALSE;
3020                 StartClocks();
3021                 continue;
3022             }
3023
3024             /* Grab player ratings from the Creating: message.
3025                Note we have to check for the special case when
3026                the ICS inserts things like [white] or [black]. */
3027             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3028                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3029                 /* star_matches:
3030                    0    player 1 name (not necessarily white)
3031                    1    player 1 rating
3032                    2    empty, white, or black (IGNORED)
3033                    3    player 2 name (not necessarily black)
3034                    4    player 2 rating
3035
3036                    The names/ratings are sorted out when the game
3037                    actually starts (below).
3038                 */
3039                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3040                 player1Rating = string_to_rating(star_match[1]);
3041                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3042                 player2Rating = string_to_rating(star_match[4]);
3043
3044                 if (appData.debugMode)
3045                   fprintf(debugFP,
3046                           "Ratings from 'Creating:' %s %d, %s %d\n",
3047                           player1Name, player1Rating,
3048                           player2Name, player2Rating);
3049
3050                 continue;
3051             }
3052
3053             /* Improved generic start/end-of-game messages */
3054             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3055                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3056                 /* If tkind == 0: */
3057                 /* star_match[0] is the game number */
3058                 /*           [1] is the white player's name */
3059                 /*           [2] is the black player's name */
3060                 /* For end-of-game: */
3061                 /*           [3] is the reason for the game end */
3062                 /*           [4] is a PGN end game-token, preceded by " " */
3063                 /* For start-of-game: */
3064                 /*           [3] begins with "Creating" or "Continuing" */
3065                 /*           [4] is " *" or empty (don't care). */
3066                 int gamenum = atoi(star_match[0]);
3067                 char *whitename, *blackname, *why, *endtoken;
3068                 ChessMove endtype = (ChessMove) 0;
3069
3070                 if (tkind == 0) {
3071                   whitename = star_match[1];
3072                   blackname = star_match[2];
3073                   why = star_match[3];
3074                   endtoken = star_match[4];
3075                 } else {
3076                   whitename = star_match[1];
3077                   blackname = star_match[3];
3078                   why = star_match[5];
3079                   endtoken = star_match[6];
3080                 }
3081
3082                 /* Game start messages */
3083                 if (strncmp(why, "Creating ", 9) == 0 ||
3084                     strncmp(why, "Continuing ", 11) == 0) {
3085                     gs_gamenum = gamenum;
3086                     strcpy(gs_kind, strchr(why, ' ') + 1);
3087 #if ZIPPY
3088                     if (appData.zippyPlay) {
3089                         ZippyGameStart(whitename, blackname);
3090                     }
3091 #endif /*ZIPPY*/
3092                     continue;
3093                 }
3094
3095                 /* Game end messages */
3096                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3097                     ics_gamenum != gamenum) {
3098                     continue;
3099                 }
3100                 while (endtoken[0] == ' ') endtoken++;
3101                 switch (endtoken[0]) {
3102                   case '*':
3103                   default:
3104                     endtype = GameUnfinished;
3105                     break;
3106                   case '0':
3107                     endtype = BlackWins;
3108                     break;
3109                   case '1':
3110                     if (endtoken[1] == '/')
3111                       endtype = GameIsDrawn;
3112                     else
3113                       endtype = WhiteWins;
3114                     break;
3115                 }
3116                 GameEnds(endtype, why, GE_ICS);
3117 #if ZIPPY
3118                 if (appData.zippyPlay && first.initDone) {
3119                     ZippyGameEnd(endtype, why);
3120                     if (first.pr == NULL) {
3121                       /* Start the next process early so that we'll
3122                          be ready for the next challenge */
3123                       StartChessProgram(&first);
3124                     }
3125                     /* Send "new" early, in case this command takes
3126                        a long time to finish, so that we'll be ready
3127                        for the next challenge. */
3128                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3129                     Reset(TRUE, TRUE);
3130                 }
3131 #endif /*ZIPPY*/
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "Removing game * from observation") ||
3136                 looking_at(buf, &i, "no longer observing game *") ||
3137                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3138                 if (gameMode == IcsObserving &&
3139                     atoi(star_match[0]) == ics_gamenum)
3140                   {
3141                       /* icsEngineAnalyze */
3142                       if (appData.icsEngineAnalyze) {
3143                             ExitAnalyzeMode();
3144                             ModeHighlight();
3145                       }
3146                       StopClocks();
3147                       gameMode = IcsIdle;
3148                       ics_gamenum = -1;
3149                       ics_user_moved = FALSE;
3150                   }
3151                 continue;
3152             }
3153
3154             if (looking_at(buf, &i, "no longer examining game *")) {
3155                 if (gameMode == IcsExamining &&
3156                     atoi(star_match[0]) == ics_gamenum)
3157                   {
3158                       gameMode = IcsIdle;
3159                       ics_gamenum = -1;
3160                       ics_user_moved = FALSE;
3161                   }
3162                 continue;
3163             }
3164
3165             /* Advance leftover_start past any newlines we find,
3166                so only partial lines can get reparsed */
3167             if (looking_at(buf, &i, "\n")) {
3168                 prevColor = curColor;
3169                 if (curColor != ColorNormal) {
3170                     if (oldi > next_out) {
3171                         SendToPlayer(&buf[next_out], oldi - next_out);
3172                         next_out = oldi;
3173                     }
3174                     Colorize(ColorNormal, FALSE);
3175                     curColor = ColorNormal;
3176                 }
3177                 if (started == STARTED_BOARD) {
3178                     started = STARTED_NONE;
3179                     parse[parse_pos] = NULLCHAR;
3180                     ParseBoard12(parse);
3181                     ics_user_moved = 0;
3182
3183                     /* Send premove here */
3184                     if (appData.premove) {
3185                       char str[MSG_SIZ];
3186                       if (currentMove == 0 &&
3187                           gameMode == IcsPlayingWhite &&
3188                           appData.premoveWhite) {
3189                         sprintf(str, "%s%s\n", ics_prefix,
3190                                 appData.premoveWhiteText);
3191                         if (appData.debugMode)
3192                           fprintf(debugFP, "Sending premove:\n");
3193                         SendToICS(str);
3194                       } else if (currentMove == 1 &&
3195                                  gameMode == IcsPlayingBlack &&
3196                                  appData.premoveBlack) {
3197                         sprintf(str, "%s%s\n", ics_prefix,
3198                                 appData.premoveBlackText);
3199                         if (appData.debugMode)
3200                           fprintf(debugFP, "Sending premove:\n");
3201                         SendToICS(str);
3202                       } else if (gotPremove) {
3203                         gotPremove = 0;
3204                         ClearPremoveHighlights();
3205                         if (appData.debugMode)
3206                           fprintf(debugFP, "Sending premove:\n");
3207                           UserMoveEvent(premoveFromX, premoveFromY,
3208                                         premoveToX, premoveToY,
3209                                         premovePromoChar);
3210                       }
3211                     }
3212
3213                     /* Usually suppress following prompt */
3214                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3215                         if (looking_at(buf, &i, "*% ")) {
3216                             savingComment = FALSE;
3217                         }
3218                     }
3219                     next_out = i;
3220                 } else if (started == STARTED_HOLDINGS) {
3221                     int gamenum;
3222                     char new_piece[MSG_SIZ];
3223                     started = STARTED_NONE;
3224                     parse[parse_pos] = NULLCHAR;
3225                     if (appData.debugMode)
3226                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3227                                                         parse, currentMove);
3228                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3229                         gamenum == ics_gamenum) {
3230                         if (gameInfo.variant == VariantNormal) {
3231                           /* [HGM] We seem to switch variant during a game!
3232                            * Presumably no holdings were displayed, so we have
3233                            * to move the position two files to the right to
3234                            * create room for them!
3235                            */
3236                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3237                           /* Get a move list just to see the header, which
3238                              will tell us whether this is really bug or zh */
3239                           if (ics_getting_history == H_FALSE) {
3240                             ics_getting_history = H_REQUESTED;
3241                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3242                             SendToICS(str);
3243                           }
3244                         }
3245                         new_piece[0] = NULLCHAR;
3246                         sscanf(parse, "game %d white [%s black [%s <- %s",
3247                                &gamenum, white_holding, black_holding,
3248                                new_piece);
3249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3251                         /* [HGM] copy holdings to board holdings area */
3252                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3253                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3254 #if ZIPPY
3255                         if (appData.zippyPlay && first.initDone) {
3256                             ZippyHoldings(white_holding, black_holding,
3257                                           new_piece);
3258                         }
3259 #endif /*ZIPPY*/
3260                         if (tinyLayout || smallLayout) {
3261                             char wh[16], bh[16];
3262                             PackHolding(wh, white_holding);
3263                             PackHolding(bh, black_holding);
3264                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3265                                     gameInfo.white, gameInfo.black);
3266                         } else {
3267                             sprintf(str, "%s [%s] vs. %s [%s]",
3268                                     gameInfo.white, white_holding,
3269                                     gameInfo.black, black_holding);
3270                         }
3271
3272                         DrawPosition(FALSE, boards[currentMove]);
3273                         DisplayTitle(str);
3274                     }
3275                     /* Suppress following prompt */
3276                     if (looking_at(buf, &i, "*% ")) {
3277                         savingComment = FALSE;
3278                     }
3279                     next_out = i;
3280                 }
3281                 continue;
3282             }
3283
3284             i++;                /* skip unparsed character and loop back */
3285         }
3286
3287         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3288             started != STARTED_HOLDINGS && i > next_out) {
3289             SendToPlayer(&buf[next_out], i - next_out);
3290             next_out = i;
3291         }
3292         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3293
3294         leftover_len = buf_len - leftover_start;
3295         /* if buffer ends with something we couldn't parse,
3296            reparse it after appending the next read */
3297
3298     } else if (count == 0) {
3299         RemoveInputSource(isr);
3300         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3301     } else {
3302         DisplayFatalError(_("Error reading from ICS"), error, 1);
3303     }
3304 }
3305
3306
3307 /* Board style 12 looks like this:
3308
3309    <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
3310
3311  * The "<12> " is stripped before it gets to this routine.  The two
3312  * trailing 0's (flip state and clock ticking) are later addition, and
3313  * some chess servers may not have them, or may have only the first.
3314  * Additional trailing fields may be added in the future.
3315  */
3316
3317 #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"
3318
3319 #define RELATION_OBSERVING_PLAYED    0
3320 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3321 #define RELATION_PLAYING_MYMOVE      1
3322 #define RELATION_PLAYING_NOTMYMOVE  -1
3323 #define RELATION_EXAMINING           2
3324 #define RELATION_ISOLATED_BOARD     -3
3325 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3326
3327 void
3328 ParseBoard12(string)
3329      char *string;
3330 {
3331     GameMode newGameMode;
3332     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3333     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3334     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3335     char to_play, board_chars[200];
3336     char move_str[500], str[500], elapsed_time[500];
3337     char black[32], white[32];
3338     Board board;
3339     int prevMove = currentMove;
3340     int ticking = 2;
3341     ChessMove moveType;
3342     int fromX, fromY, toX, toY;
3343     char promoChar;
3344     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3345     char *bookHit = NULL; // [HGM] book
3346
3347     fromX = fromY = toX = toY = -1;
3348
3349     newGame = FALSE;
3350
3351     if (appData.debugMode)
3352       fprintf(debugFP, _("Parsing board: %s\n"), string);
3353
3354     move_str[0] = NULLCHAR;
3355     elapsed_time[0] = NULLCHAR;
3356     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3357         int  i = 0, j;
3358         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3359             if(string[i] == ' ') { ranks++; files = 0; }
3360             else files++;
3361             i++;
3362         }
3363         for(j = 0; j <i; j++) board_chars[j] = string[j];
3364         board_chars[i] = '\0';
3365         string += i + 1;
3366     }
3367     n = sscanf(string, PATTERN, &to_play, &double_push,
3368                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3369                &gamenum, white, black, &relation, &basetime, &increment,
3370                &white_stren, &black_stren, &white_time, &black_time,
3371                &moveNum, str, elapsed_time, move_str, &ics_flip,
3372                &ticking);
3373
3374     if (n < 21) {
3375         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3376         DisplayError(str, 0);
3377         return;
3378     }
3379
3380     /* Convert the move number to internal form */
3381     moveNum = (moveNum - 1) * 2;
3382     if (to_play == 'B') moveNum++;
3383     if (moveNum >= MAX_MOVES) {
3384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3385                         0, 1);
3386       return;
3387     }
3388
3389     switch (relation) {
3390       case RELATION_OBSERVING_PLAYED:
3391       case RELATION_OBSERVING_STATIC:
3392         if (gamenum == -1) {
3393             /* Old ICC buglet */
3394             relation = RELATION_OBSERVING_STATIC;
3395         }
3396         newGameMode = IcsObserving;
3397         break;
3398       case RELATION_PLAYING_MYMOVE:
3399       case RELATION_PLAYING_NOTMYMOVE:
3400         newGameMode =
3401           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3402             IcsPlayingWhite : IcsPlayingBlack;
3403         break;
3404       case RELATION_EXAMINING:
3405         newGameMode = IcsExamining;
3406         break;
3407       case RELATION_ISOLATED_BOARD:
3408       default:
3409         /* Just display this board.  If user was doing something else,
3410            we will forget about it until the next board comes. */
3411         newGameMode = IcsIdle;
3412         break;
3413       case RELATION_STARTING_POSITION:
3414         newGameMode = gameMode;
3415         break;
3416     }
3417
3418     /* Modify behavior for initial board display on move listing
3419        of wild games.
3420        */
3421     switch (ics_getting_history) {
3422       case H_FALSE:
3423       case H_REQUESTED:
3424         break;
3425       case H_GOT_REQ_HEADER:
3426       case H_GOT_UNREQ_HEADER:
3427         /* This is the initial position of the current game */
3428         gamenum = ics_gamenum;
3429         moveNum = 0;            /* old ICS bug workaround */
3430         if (to_play == 'B') {
3431           startedFromSetupPosition = TRUE;
3432           blackPlaysFirst = TRUE;
3433           moveNum = 1;
3434           if (forwardMostMove == 0) forwardMostMove = 1;
3435           if (backwardMostMove == 0) backwardMostMove = 1;
3436           if (currentMove == 0) currentMove = 1;
3437         }
3438         newGameMode = gameMode;
3439         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3440         break;
3441       case H_GOT_UNWANTED_HEADER:
3442         /* This is an initial board that we don't want */
3443         return;
3444       case H_GETTING_MOVES:
3445         /* Should not happen */
3446         DisplayError(_("Error gathering move list: extra board"), 0);
3447         ics_getting_history = H_FALSE;
3448         return;
3449     }
3450
3451     /* Take action if this is the first board of a new game, or of a
3452        different game than is currently being displayed.  */
3453     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3454         relation == RELATION_ISOLATED_BOARD) {
3455
3456         /* Forget the old game and get the history (if any) of the new one */
3457         if (gameMode != BeginningOfGame) {
3458           Reset(FALSE, TRUE);
3459         }
3460         newGame = TRUE;
3461         if (appData.autoRaiseBoard) BoardToTop();
3462         prevMove = -3;
3463         if (gamenum == -1) {
3464             newGameMode = IcsIdle;
3465         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3466                    appData.getMoveList) {
3467             /* Need to get game history */
3468             ics_getting_history = H_REQUESTED;
3469             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3470             SendToICS(str);
3471         }
3472
3473         /* Initially flip the board to have black on the bottom if playing
3474            black or if the ICS flip flag is set, but let the user change
3475            it with the Flip View button. */
3476         flipView = appData.autoFlipView ?
3477           (newGameMode == IcsPlayingBlack) || ics_flip :
3478           appData.flipView;
3479
3480         /* Done with values from previous mode; copy in new ones */
3481         gameMode = newGameMode;
3482         ModeHighlight();
3483         ics_gamenum = gamenum;
3484         if (gamenum == gs_gamenum) {
3485             int klen = strlen(gs_kind);
3486             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3487             sprintf(str, "ICS %s", gs_kind);
3488             gameInfo.event = StrSave(str);
3489         } else {
3490             gameInfo.event = StrSave("ICS game");
3491         }
3492         gameInfo.site = StrSave(appData.icsHost);
3493         gameInfo.date = PGNDate();
3494         gameInfo.round = StrSave("-");
3495         gameInfo.white = StrSave(white);
3496         gameInfo.black = StrSave(black);
3497         timeControl = basetime * 60 * 1000;
3498         timeControl_2 = 0;
3499         timeIncrement = increment * 1000;
3500         movesPerSession = 0;
3501         gameInfo.timeControl = TimeControlTagValue();
3502         VariantSwitch(board, StringToVariant(gameInfo.event) );
3503   if (appData.debugMode) {
3504     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3505     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3506     setbuf(debugFP, NULL);
3507   }
3508
3509         gameInfo.outOfBook = NULL;
3510
3511         /* Do we have the ratings? */
3512         if (strcmp(player1Name, white) == 0 &&
3513             strcmp(player2Name, black) == 0) {
3514             if (appData.debugMode)
3515               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3516                       player1Rating, player2Rating);
3517             gameInfo.whiteRating = player1Rating;
3518             gameInfo.blackRating = player2Rating;
3519         } else if (strcmp(player2Name, white) == 0 &&
3520                    strcmp(player1Name, black) == 0) {
3521             if (appData.debugMode)
3522               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3523                       player2Rating, player1Rating);
3524             gameInfo.whiteRating = player2Rating;
3525             gameInfo.blackRating = player1Rating;
3526         }
3527         player1Name[0] = player2Name[0] = NULLCHAR;
3528
3529         /* Silence shouts if requested */
3530         if (appData.quietPlay &&
3531             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3532             SendToICS(ics_prefix);
3533             SendToICS("set shout 0\n");
3534         }
3535     }
3536
3537     /* Deal with midgame name changes */
3538     if (!newGame) {
3539         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3540             if (gameInfo.white) free(gameInfo.white);
3541             gameInfo.white = StrSave(white);
3542         }
3543         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3544             if (gameInfo.black) free(gameInfo.black);
3545             gameInfo.black = StrSave(black);
3546         }
3547     }
3548
3549     /* Throw away game result if anything actually changes in examine mode */
3550     if (gameMode == IcsExamining && !newGame) {
3551         gameInfo.result = GameUnfinished;
3552         if (gameInfo.resultDetails != NULL) {
3553             free(gameInfo.resultDetails);
3554             gameInfo.resultDetails = NULL;
3555         }
3556     }
3557
3558     /* In pausing && IcsExamining mode, we ignore boards coming
3559        in if they are in a different variation than we are. */
3560     if (pauseExamInvalid) return;
3561     if (pausing && gameMode == IcsExamining) {
3562         if (moveNum <= pauseExamForwardMostMove) {
3563             pauseExamInvalid = TRUE;
3564             forwardMostMove = pauseExamForwardMostMove;
3565             return;
3566         }
3567     }
3568
3569   if (appData.debugMode) {
3570     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3571   }
3572     /* Parse the board */
3573     for (k = 0; k < ranks; k++) {
3574       for (j = 0; j < files; j++)
3575         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3576       if(gameInfo.holdingsWidth > 1) {
3577            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3578            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3579       }
3580     }
3581     CopyBoard(boards[moveNum], board);
3582     if (moveNum == 0) {
3583         startedFromSetupPosition =
3584           !CompareBoards(board, initialPosition);
3585         if(startedFromSetupPosition)
3586             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3587     }
3588
3589     /* [HGM] Set castling rights. Take the outermost Rooks,
3590        to make it also work for FRC opening positions. Note that board12
3591        is really defective for later FRC positions, as it has no way to
3592        indicate which Rook can castle if they are on the same side of King.
3593        For the initial position we grant rights to the outermost Rooks,
3594        and remember thos rights, and we then copy them on positions
3595        later in an FRC game. This means WB might not recognize castlings with
3596        Rooks that have moved back to their original position as illegal,
3597        but in ICS mode that is not its job anyway.
3598     */
3599     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3600     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3601
3602         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3603             if(board[0][i] == WhiteRook) j = i;
3604         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3606             if(board[0][i] == WhiteRook) j = i;
3607         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3609             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3610         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3611         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3612             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3613         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3614
3615         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3616         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3617             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3618         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3619             if(board[BOARD_HEIGHT-1][k] == bKing)
3620                 initialRights[5] = castlingRights[moveNum][5] = k;
3621     } else { int r;
3622         r = castlingRights[moveNum][0] = initialRights[0];
3623         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3624         r = castlingRights[moveNum][1] = initialRights[1];
3625         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3626         r = castlingRights[moveNum][3] = initialRights[3];
3627         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3628         r = castlingRights[moveNum][4] = initialRights[4];
3629         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3630         /* wildcastle kludge: always assume King has rights */
3631         r = castlingRights[moveNum][2] = initialRights[2];
3632         r = castlingRights[moveNum][5] = initialRights[5];
3633     }
3634     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3635     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3636
3637
3638     if (ics_getting_history == H_GOT_REQ_HEADER ||
3639         ics_getting_history == H_GOT_UNREQ_HEADER) {
3640         /* This was an initial position from a move list, not
3641            the current position */
3642         return;
3643     }
3644
3645     /* Update currentMove and known move number limits */
3646     newMove = newGame || moveNum > forwardMostMove;
3647
3648     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3649     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3650         takeback = forwardMostMove - moveNum;
3651         for (i = 0; i < takeback; i++) {
3652              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3653              SendToProgram("undo\n", &first);
3654         }
3655     }
3656
3657     if (newGame) {
3658         forwardMostMove = backwardMostMove = currentMove = moveNum;
3659         if (gameMode == IcsExamining && moveNum == 0) {
3660           /* Workaround for ICS limitation: we are not told the wild
3661              type when starting to examine a game.  But if we ask for
3662              the move list, the move list header will tell us */
3663             ics_getting_history = H_REQUESTED;
3664             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3665             SendToICS(str);
3666         }
3667     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3668                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3669         forwardMostMove = moveNum;
3670         if (!pausing || currentMove > forwardMostMove)
3671           currentMove = forwardMostMove;
3672     } else {
3673         /* New part of history that is not contiguous with old part */
3674         if (pausing && gameMode == IcsExamining) {
3675             pauseExamInvalid = TRUE;
3676             forwardMostMove = pauseExamForwardMostMove;
3677             return;
3678         }
3679         forwardMostMove = backwardMostMove = currentMove = moveNum;
3680         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3681             ics_getting_history = H_REQUESTED;
3682             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3683             SendToICS(str);
3684         }
3685     }
3686
3687     /* Update the clocks */
3688     if (strchr(elapsed_time, '.')) {
3689       /* Time is in ms */
3690       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3691       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3692     } else {
3693       /* Time is in seconds */
3694       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3695       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3696     }
3697
3698
3699 #if ZIPPY
3700     if (appData.zippyPlay && newGame &&
3701         gameMode != IcsObserving && gameMode != IcsIdle &&
3702         gameMode != IcsExamining)
3703       ZippyFirstBoard(moveNum, basetime, increment);
3704 #endif
3705
3706     /* Put the move on the move list, first converting
3707        to canonical algebraic form. */
3708     if (moveNum > 0) {
3709   if (appData.debugMode) {
3710     if (appData.debugMode) { int f = forwardMostMove;
3711         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3712                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3713     }
3714     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3715     fprintf(debugFP, "moveNum = %d\n", moveNum);
3716     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3717     setbuf(debugFP, NULL);
3718   }
3719         if (moveNum <= backwardMostMove) {
3720             /* We don't know what the board looked like before
3721                this move.  Punt. */
3722             strcpy(parseList[moveNum - 1], move_str);
3723             strcat(parseList[moveNum - 1], " ");
3724             strcat(parseList[moveNum - 1], elapsed_time);
3725             moveList[moveNum - 1][0] = NULLCHAR;
3726         } else if (strcmp(move_str, "none") == 0) {
3727             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3728             /* Again, we don't know what the board looked like;
3729                this is really the start of the game. */
3730             parseList[moveNum - 1][0] = NULLCHAR;
3731             moveList[moveNum - 1][0] = NULLCHAR;
3732             backwardMostMove = moveNum;
3733             startedFromSetupPosition = TRUE;
3734             fromX = fromY = toX = toY = -1;
3735         } else {
3736           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3737           //                 So we parse the long-algebraic move string in stead of the SAN move
3738           int valid; char buf[MSG_SIZ], *prom;
3739
3740           // str looks something like "Q/a1-a2"; kill the slash
3741           if(str[1] == '/')
3742                 sprintf(buf, "%c%s", str[0], str+2);
3743           else  strcpy(buf, str); // might be castling
3744           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3745                 strcat(buf, prom); // long move lacks promo specification!
3746           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3747                 if(appData.debugMode)
3748                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3749                 strcpy(move_str, buf);
3750           }
3751           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3752                                 &fromX, &fromY, &toX, &toY, &promoChar)
3753                || ParseOneMove(buf, moveNum - 1, &moveType,
3754                                 &fromX, &fromY, &toX, &toY, &promoChar);
3755           // end of long SAN patch
3756           if (valid) {
3757             (void) CoordsToAlgebraic(boards[moveNum - 1],
3758                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3759                                      fromY, fromX, toY, toX, promoChar,
3760                                      parseList[moveNum-1]);
3761             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3762                              castlingRights[moveNum]) ) {
3763               case MT_NONE:
3764               case MT_STALEMATE:
3765               default:
3766                 break;
3767               case MT_CHECK:
3768                 if(gameInfo.variant != VariantShogi)
3769                     strcat(parseList[moveNum - 1], "+");
3770                 break;
3771               case MT_CHECKMATE:
3772               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3773                 strcat(parseList[moveNum - 1], "#");
3774                 break;
3775             }
3776             strcat(parseList[moveNum - 1], " ");
3777             strcat(parseList[moveNum - 1], elapsed_time);
3778             /* currentMoveString is set as a side-effect of ParseOneMove */
3779             strcpy(moveList[moveNum - 1], currentMoveString);
3780             strcat(moveList[moveNum - 1], "\n");
3781           } else {
3782             /* Move from ICS was illegal!?  Punt. */
3783   if (appData.debugMode) {
3784     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3785     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3786   }
3787             strcpy(parseList[moveNum - 1], move_str);
3788             strcat(parseList[moveNum - 1], " ");
3789             strcat(parseList[moveNum - 1], elapsed_time);
3790             moveList[moveNum - 1][0] = NULLCHAR;
3791             fromX = fromY = toX = toY = -1;
3792           }
3793         }
3794   if (appData.debugMode) {
3795     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3796     setbuf(debugFP, NULL);
3797   }
3798
3799 #if ZIPPY
3800         /* Send move to chess program (BEFORE animating it). */
3801         if (appData.zippyPlay && !newGame && newMove &&
3802            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3803
3804             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3805                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3806                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3807                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3808                             move_str);
3809                     DisplayError(str, 0);
3810                 } else {
3811                     if (first.sendTime) {
3812                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3813                     }
3814                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3815                     if (firstMove && !bookHit) {
3816                         firstMove = FALSE;
3817                         if (first.useColors) {
3818                           SendToProgram(gameMode == IcsPlayingWhite ?
3819                                         "white\ngo\n" :
3820                                         "black\ngo\n", &first);
3821                         } else {
3822                           SendToProgram("go\n", &first);
3823                         }
3824                         first.maybeThinking = TRUE;
3825                     }
3826                 }
3827             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3828               if (moveList[moveNum - 1][0] == NULLCHAR) {
3829                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3830                 DisplayError(str, 0);
3831               } else {
3832                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3833                 SendMoveToProgram(moveNum - 1, &first);
3834               }
3835             }
3836         }
3837 #endif
3838     }
3839
3840     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3841         /* If move comes from a remote source, animate it.  If it
3842            isn't remote, it will have already been animated. */
3843         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3844             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3845         }
3846         if (!pausing && appData.highlightLastMove) {
3847             SetHighlights(fromX, fromY, toX, toY);
3848         }
3849     }
3850
3851     /* Start the clocks */
3852     whiteFlag = blackFlag = FALSE;
3853     appData.clockMode = !(basetime == 0 && increment == 0);
3854     if (ticking == 0) {
3855       ics_clock_paused = TRUE;
3856       StopClocks();
3857     } else if (ticking == 1) {
3858       ics_clock_paused = FALSE;
3859     }
3860     if (gameMode == IcsIdle ||
3861         relation == RELATION_OBSERVING_STATIC ||
3862         relation == RELATION_EXAMINING ||
3863         ics_clock_paused)
3864       DisplayBothClocks();
3865     else
3866       StartClocks();
3867
3868     /* Display opponents and material strengths */
3869     if (gameInfo.variant != VariantBughouse &&
3870         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3871         if (tinyLayout || smallLayout) {
3872             if(gameInfo.variant == VariantNormal)
3873                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment);
3876             else
3877                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3878                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3879                     basetime, increment, (int) gameInfo.variant);
3880         } else {
3881             if(gameInfo.variant == VariantNormal)
3882                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3883                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3884                     basetime, increment);
3885             else
3886                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3887                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3888                     basetime, increment, VariantName(gameInfo.variant));
3889         }
3890         DisplayTitle(str);
3891   if (appData.debugMode) {
3892     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3893   }
3894     }
3895
3896
3897     /* Display the board */
3898     if (!pausing && !appData.noGUI) {
3899       if (appData.premove)
3900           if (!gotPremove ||
3901              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3902              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3903               ClearPremoveHighlights();
3904
3905       DrawPosition(FALSE, boards[currentMove]);
3906       DisplayMove(moveNum - 1);
3907       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3908             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3909               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3910         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3911       }
3912     }
3913
3914     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3915 #if ZIPPY
3916     if(bookHit) { // [HGM] book: simulate book reply
3917         static char bookMove[MSG_SIZ]; // a bit generous?
3918
3919         programStats.nodes = programStats.depth = programStats.time =
3920         programStats.score = programStats.got_only_move = 0;
3921         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3922
3923         strcpy(bookMove, "move ");
3924         strcat(bookMove, bookHit);
3925         HandleMachineMove(bookMove, &first);
3926     }
3927 #endif
3928 }
3929
3930 void
3931 GetMoveListEvent()
3932 {
3933     char buf[MSG_SIZ];
3934     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3935         ics_getting_history = H_REQUESTED;
3936         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3937         SendToICS(buf);
3938     }
3939 }
3940
3941 void
3942 AnalysisPeriodicEvent(force)
3943      int force;
3944 {
3945     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3946          && !force) || !appData.periodicUpdates)
3947       return;
3948
3949     /* Send . command to Crafty to collect stats */
3950     SendToProgram(".\n", &first);
3951
3952     /* Don't send another until we get a response (this makes
3953        us stop sending to old Crafty's which don't understand
3954        the "." command (sending illegal cmds resets node count & time,
3955        which looks bad)) */
3956     programStats.ok_to_send = 0;
3957 }
3958
3959 void ics_update_width(new_width)
3960         int new_width;
3961 {
3962         ics_printf("set width %d\n", new_width);
3963 }
3964
3965 void
3966 SendMoveToProgram(moveNum, cps)
3967      int moveNum;
3968      ChessProgramState *cps;
3969 {
3970     char buf[MSG_SIZ];
3971
3972     if (cps->useUsermove) {
3973       SendToProgram("usermove ", cps);
3974     }
3975     if (cps->useSAN) {
3976       char *space;
3977       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3978         int len = space - parseList[moveNum];
3979         memcpy(buf, parseList[moveNum], len);
3980         buf[len++] = '\n';
3981         buf[len] = NULLCHAR;
3982       } else {
3983         sprintf(buf, "%s\n", parseList[moveNum]);
3984       }
3985       SendToProgram(buf, cps);
3986     } else {
3987       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3988         AlphaRank(moveList[moveNum], 4);
3989         SendToProgram(moveList[moveNum], cps);
3990         AlphaRank(moveList[moveNum], 4); // and back
3991       } else
3992       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3993        * the engine. It would be nice to have a better way to identify castle
3994        * moves here. */
3995       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3996                                                                          && cps->useOOCastle) {
3997         int fromX = moveList[moveNum][0] - AAA;
3998         int fromY = moveList[moveNum][1] - ONE;
3999         int toX = moveList[moveNum][2] - AAA;
4000         int toY = moveList[moveNum][3] - ONE;
4001         if((boards[moveNum][fromY][fromX] == WhiteKing
4002             && boards[moveNum][toY][toX] == WhiteRook)
4003            || (boards[moveNum][fromY][fromX] == BlackKing
4004                && boards[moveNum][toY][toX] == BlackRook)) {
4005           if(toX > fromX) SendToProgram("O-O\n", cps);
4006           else SendToProgram("O-O-O\n", cps);
4007         }
4008         else SendToProgram(moveList[moveNum], cps);
4009       }
4010       else SendToProgram(moveList[moveNum], cps);
4011       /* End of additions by Tord */
4012     }
4013
4014     /* [HGM] setting up the opening has brought engine in force mode! */
4015     /*       Send 'go' if we are in a mode where machine should play. */
4016     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4017         (gameMode == TwoMachinesPlay   ||
4018 #ifdef ZIPPY
4019          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4020 #endif
4021          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4022         SendToProgram("go\n", cps);
4023   if (appData.debugMode) {
4024     fprintf(debugFP, "(extra)\n");
4025   }
4026     }
4027     setboardSpoiledMachineBlack = 0;
4028 }
4029
4030 void
4031 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4032      ChessMove moveType;
4033      int fromX, fromY, toX, toY;
4034 {
4035     char user_move[MSG_SIZ];
4036
4037     switch (moveType) {
4038       default:
4039         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4040                 (int)moveType, fromX, fromY, toX, toY);
4041         DisplayError(user_move + strlen("say "), 0);
4042         break;
4043       case WhiteKingSideCastle:
4044       case BlackKingSideCastle:
4045       case WhiteQueenSideCastleWild:
4046       case BlackQueenSideCastleWild:
4047       /* PUSH Fabien */
4048       case WhiteHSideCastleFR:
4049       case BlackHSideCastleFR:
4050       /* POP Fabien */
4051         sprintf(user_move, "o-o\n");
4052         break;
4053       case WhiteQueenSideCastle:
4054       case BlackQueenSideCastle:
4055       case WhiteKingSideCastleWild:
4056       case BlackKingSideCastleWild:
4057       /* PUSH Fabien */
4058       case WhiteASideCastleFR:
4059       case BlackASideCastleFR:
4060       /* POP Fabien */
4061         sprintf(user_move, "o-o-o\n");
4062         break;
4063       case WhitePromotionQueen:
4064       case BlackPromotionQueen:
4065       case WhitePromotionRook:
4066       case BlackPromotionRook:
4067       case WhitePromotionBishop:
4068       case BlackPromotionBishop:
4069       case WhitePromotionKnight:
4070       case BlackPromotionKnight:
4071       case WhitePromotionKing:
4072       case BlackPromotionKing:
4073       case WhitePromotionChancellor:
4074       case BlackPromotionChancellor:
4075       case WhitePromotionArchbishop:
4076       case BlackPromotionArchbishop:
4077         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4078             sprintf(user_move, "%c%c%c%c=%c\n",
4079                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4080                 PieceToChar(WhiteFerz));
4081         else if(gameInfo.variant == VariantGreat)
4082             sprintf(user_move, "%c%c%c%c=%c\n",
4083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4084                 PieceToChar(WhiteMan));
4085         else
4086             sprintf(user_move, "%c%c%c%c=%c\n",
4087                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4088                 PieceToChar(PromoPiece(moveType)));
4089         break;
4090       case WhiteDrop:
4091       case BlackDrop:
4092         sprintf(user_move, "%c@%c%c\n",
4093                 ToUpper(PieceToChar((ChessSquare) fromX)),
4094                 AAA + toX, ONE + toY);
4095         break;
4096       case NormalMove:
4097       case WhiteCapturesEnPassant:
4098       case BlackCapturesEnPassant:
4099       case IllegalMove:  /* could be a variant we don't quite understand */
4100         sprintf(user_move, "%c%c%c%c\n",
4101                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4102         break;
4103     }
4104     SendToICS(user_move);
4105     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4106         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4107 }
4108
4109 void
4110 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4111      int rf, ff, rt, ft;
4112      char promoChar;
4113      char move[7];
4114 {
4115     if (rf == DROP_RANK) {
4116         sprintf(move, "%c@%c%c\n",
4117                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4118     } else {
4119         if (promoChar == 'x' || promoChar == NULLCHAR) {
4120             sprintf(move, "%c%c%c%c\n",
4121                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4122         } else {
4123             sprintf(move, "%c%c%c%c%c\n",
4124                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4125         }
4126     }
4127 }
4128
4129 void
4130 ProcessICSInitScript(f)
4131      FILE *f;
4132 {
4133     char buf[MSG_SIZ];
4134
4135     while (fgets(buf, MSG_SIZ, f)) {
4136         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4137     }
4138
4139     fclose(f);
4140 }
4141
4142
4143 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4144 void
4145 AlphaRank(char *move, int n)
4146 {
4147 //    char *p = move, c; int x, y;
4148
4149     if (appData.debugMode) {
4150         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4151     }
4152
4153     if(move[1]=='*' &&
4154        move[2]>='0' && move[2]<='9' &&
4155        move[3]>='a' && move[3]<='x'    ) {
4156         move[1] = '@';
4157         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4158         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4159     } else
4160     if(move[0]>='0' && move[0]<='9' &&
4161        move[1]>='a' && move[1]<='x' &&
4162        move[2]>='0' && move[2]<='9' &&
4163        move[3]>='a' && move[3]<='x'    ) {
4164         /* input move, Shogi -> normal */
4165         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4166         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4167         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4168         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4169     } else
4170     if(move[1]=='@' &&
4171        move[3]>='0' && move[3]<='9' &&
4172        move[2]>='a' && move[2]<='x'    ) {
4173         move[1] = '*';
4174         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4175         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4176     } else
4177     if(
4178        move[0]>='a' && move[0]<='x' &&
4179        move[3]>='0' && move[3]<='9' &&
4180        move[2]>='a' && move[2]<='x'    ) {
4181          /* output move, normal -> Shogi */
4182         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4183         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4184         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4185         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4186         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4187     }
4188     if (appData.debugMode) {
4189         fprintf(debugFP, "   out = '%s'\n", move);
4190     }
4191 }
4192
4193 /* Parser for moves from gnuchess, ICS, or user typein box */
4194 Boolean
4195 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4196      char *move;
4197      int moveNum;
4198      ChessMove *moveType;
4199      int *fromX, *fromY, *toX, *toY;
4200      char *promoChar;
4201 {
4202     if (appData.debugMode) {
4203         fprintf(debugFP, "move to parse: %s\n", move);
4204     }
4205     *moveType = yylexstr(moveNum, move);
4206
4207     switch (*moveType) {
4208       case WhitePromotionChancellor:
4209       case BlackPromotionChancellor:
4210       case WhitePromotionArchbishop:
4211       case BlackPromotionArchbishop:
4212       case WhitePromotionQueen:
4213       case BlackPromotionQueen:
4214       case WhitePromotionRook:
4215       case BlackPromotionRook:
4216       case WhitePromotionBishop:
4217       case BlackPromotionBishop:
4218       case WhitePromotionKnight:
4219       case BlackPromotionKnight:
4220       case WhitePromotionKing:
4221       case BlackPromotionKing:
4222       case NormalMove:
4223       case WhiteCapturesEnPassant:
4224       case BlackCapturesEnPassant:
4225       case WhiteKingSideCastle:
4226       case WhiteQueenSideCastle:
4227       case BlackKingSideCastle:
4228       case BlackQueenSideCastle:
4229       case WhiteKingSideCastleWild:
4230       case WhiteQueenSideCastleWild:
4231       case BlackKingSideCastleWild:
4232       case BlackQueenSideCastleWild:
4233       /* Code added by Tord: */
4234       case WhiteHSideCastleFR:
4235       case WhiteASideCastleFR:
4236       case BlackHSideCastleFR:
4237       case BlackASideCastleFR:
4238       /* End of code added by Tord */
4239       case IllegalMove:         /* bug or odd chess variant */
4240         *fromX = currentMoveString[0] - AAA;
4241         *fromY = currentMoveString[1] - ONE;
4242         *toX = currentMoveString[2] - AAA;
4243         *toY = currentMoveString[3] - ONE;
4244         *promoChar = currentMoveString[4];
4245         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4246             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4247     if (appData.debugMode) {
4248         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4249     }
4250             *fromX = *fromY = *toX = *toY = 0;
4251             return FALSE;
4252         }
4253         if (appData.testLegality) {
4254           return (*moveType != IllegalMove);
4255         } else {
4256           return !(fromX == fromY && toX == toY);
4257         }
4258
4259       case WhiteDrop:
4260       case BlackDrop:
4261         *fromX = *moveType == WhiteDrop ?
4262           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4263           (int) CharToPiece(ToLower(currentMoveString[0]));
4264         *fromY = DROP_RANK;
4265         *toX = currentMoveString[2] - AAA;
4266         *toY = currentMoveString[3] - ONE;
4267         *promoChar = NULLCHAR;
4268         return TRUE;
4269
4270       case AmbiguousMove:
4271       case ImpossibleMove:
4272       case (ChessMove) 0:       /* end of file */
4273       case ElapsedTime:
4274       case Comment:
4275       case PGNTag:
4276       case NAG:
4277       case WhiteWins:
4278       case BlackWins:
4279       case GameIsDrawn:
4280       default:
4281     if (appData.debugMode) {
4282         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4283     }
4284         /* bug? */
4285         *fromX = *fromY = *toX = *toY = 0;
4286         *promoChar = NULLCHAR;
4287         return FALSE;
4288     }
4289 }
4290
4291 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4292 // All positions will have equal probability, but the current method will not provide a unique
4293 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4294 #define DARK 1
4295 #define LITE 2
4296 #define ANY 3
4297
4298 int squaresLeft[4];
4299 int piecesLeft[(int)BlackPawn];
4300 int seed, nrOfShuffles;
4301
4302 void GetPositionNumber()
4303 {       // sets global variable seed
4304         int i;
4305
4306         seed = appData.defaultFrcPosition;
4307         if(seed < 0) { // randomize based on time for negative FRC position numbers
4308                 for(i=0; i<50; i++) seed += random();
4309                 seed = random() ^ random() >> 8 ^ random() << 8;
4310                 if(seed<0) seed = -seed;
4311         }
4312 }
4313
4314 int put(Board board, int pieceType, int rank, int n, int shade)
4315 // put the piece on the (n-1)-th empty squares of the given shade
4316 {
4317         int i;
4318
4319         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4320                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4321                         board[rank][i] = (ChessSquare) pieceType;
4322                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4323                         squaresLeft[ANY]--;
4324                         piecesLeft[pieceType]--;
4325                         return i;
4326                 }
4327         }
4328         return -1;
4329 }
4330
4331
4332 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4333 // calculate where the next piece goes, (any empty square), and put it there
4334 {
4335         int i;
4336
4337         i = seed % squaresLeft[shade];
4338         nrOfShuffles *= squaresLeft[shade];
4339         seed /= squaresLeft[shade];
4340         put(board, pieceType, rank, i, shade);
4341 }
4342
4343 void AddTwoPieces(Board board, int pieceType, int rank)
4344 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4345 {
4346         int i, n=squaresLeft[ANY], j=n-1, k;
4347
4348         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4349         i = seed % k;  // pick one
4350         nrOfShuffles *= k;
4351         seed /= k;
4352         while(i >= j) i -= j--;
4353         j = n - 1 - j; i += j;
4354         put(board, pieceType, rank, j, ANY);
4355         put(board, pieceType, rank, i, ANY);
4356 }
4357
4358 void SetUpShuffle(Board board, int number)
4359 {
4360         int i, p, first=1;
4361
4362         GetPositionNumber(); nrOfShuffles = 1;
4363
4364         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4365         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4366         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4367
4368         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4369
4370         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4371             p = (int) board[0][i];
4372             if(p < (int) BlackPawn) piecesLeft[p] ++;
4373             board[0][i] = EmptySquare;
4374         }
4375
4376         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4377             // shuffles restricted to allow normal castling put KRR first
4378             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4379                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4380             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4381                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4382             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4383                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4384             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4385                 put(board, WhiteRook, 0, 0, ANY);
4386             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4387         }
4388
4389         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4390             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4391             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4392                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4393                 while(piecesLeft[p] >= 2) {
4394                     AddOnePiece(board, p, 0, LITE);
4395                     AddOnePiece(board, p, 0, DARK);
4396                 }
4397                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4398             }
4399
4400         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4401             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4402             // but we leave King and Rooks for last, to possibly obey FRC restriction
4403             if(p == (int)WhiteRook) continue;
4404             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4405             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4406         }
4407
4408         // now everything is placed, except perhaps King (Unicorn) and Rooks
4409
4410         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4411             // Last King gets castling rights
4412             while(piecesLeft[(int)WhiteUnicorn]) {
4413                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4414                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4415             }
4416
4417             while(piecesLeft[(int)WhiteKing]) {
4418                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4419                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4420             }
4421
4422
4423         } else {
4424             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4425             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4426         }
4427
4428         // Only Rooks can be left; simply place them all
4429         while(piecesLeft[(int)WhiteRook]) {
4430                 i = put(board, WhiteRook, 0, 0, ANY);
4431                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4432                         if(first) {
4433                                 first=0;
4434                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4435                         }
4436                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4437                 }
4438         }
4439         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4440             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4441         }
4442
4443         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4444 }
4445
4446 int SetCharTable( char *table, const char * map )
4447 /* [HGM] moved here from winboard.c because of its general usefulness */
4448 /*       Basically a safe strcpy that uses the last character as King */
4449 {
4450     int result = FALSE; int NrPieces;
4451
4452     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4453                     && NrPieces >= 12 && !(NrPieces&1)) {
4454         int i; /* [HGM] Accept even length from 12 to 34 */
4455
4456         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4457         for( i=0; i<NrPieces/2-1; i++ ) {
4458             table[i] = map[i];
4459             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4460         }
4461         table[(int) WhiteKing]  = map[NrPieces/2-1];
4462         table[(int) BlackKing]  = map[NrPieces-1];
4463
4464         result = TRUE;
4465     }
4466
4467     return result;
4468 }
4469
4470 void Prelude(Board board)
4471 {       // [HGM] superchess: random selection of exo-pieces
4472         int i, j, k; ChessSquare p;
4473         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4474
4475         GetPositionNumber(); // use FRC position number
4476
4477         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4478             SetCharTable(pieceToChar, appData.pieceToCharTable);
4479             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4480                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4481         }
4482
4483         j = seed%4;                 seed /= 4;
4484         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4485         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4486         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4487         j = seed%3 + (seed%3 >= j); seed /= 3;
4488         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4489         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4490         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4491         j = seed%3;                 seed /= 3;
4492         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4493         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4494         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4495         j = seed%2 + (seed%2 >= j); seed /= 2;
4496         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4497         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4498         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4499         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4500         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4501         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4502         put(board, exoPieces[0],    0, 0, ANY);
4503         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4504 }
4505
4506 void
4507 InitPosition(redraw)
4508      int redraw;
4509 {
4510     ChessSquare (* pieces)[BOARD_SIZE];
4511     int i, j, pawnRow, overrule,
4512     oldx = gameInfo.boardWidth,
4513     oldy = gameInfo.boardHeight,
4514     oldh = gameInfo.holdingsWidth,
4515     oldv = gameInfo.variant;
4516
4517     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4518
4519     /* [AS] Initialize pv info list [HGM] and game status */
4520     {
4521         for( i=0; i<MAX_MOVES; i++ ) {
4522             pvInfoList[i].depth = 0;
4523             epStatus[i]=EP_NONE;
4524             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4525         }
4526
4527         initialRulePlies = 0; /* 50-move counter start */
4528
4529         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4530         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4531     }
4532
4533
4534     /* [HGM] logic here is completely changed. In stead of full positions */
4535     /* the initialized data only consist of the two backranks. The switch */
4536     /* selects which one we will use, which is than copied to the Board   */
4537     /* initialPosition, which for the rest is initialized by Pawns and    */
4538     /* empty squares. This initial position is then copied to boards[0],  */
4539     /* possibly after shuffling, so that it remains available.            */
4540
4541     gameInfo.holdingsWidth = 0; /* default board sizes */
4542     gameInfo.boardWidth    = 8;
4543     gameInfo.boardHeight   = 8;
4544     gameInfo.holdingsSize  = 0;
4545     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4546     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4547     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4548
4549     switch (gameInfo.variant) {
4550     case VariantFischeRandom:
4551       shuffleOpenings = TRUE;
4552     default:
4553       pieces = FIDEArray;
4554       break;
4555     case VariantShatranj:
4556       pieces = ShatranjArray;
4557       nrCastlingRights = 0;
4558       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4559       break;
4560     case VariantTwoKings:
4561       pieces = twoKingsArray;
4562       break;
4563     case VariantCapaRandom:
4564       shuffleOpenings = TRUE;
4565     case VariantCapablanca:
4566       pieces = CapablancaArray;
4567       gameInfo.boardWidth = 10;
4568       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4569       break;
4570     case VariantGothic:
4571       pieces = GothicArray;
4572       gameInfo.boardWidth = 10;
4573       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4574       break;
4575     case VariantJanus:
4576       pieces = JanusArray;
4577       gameInfo.boardWidth = 10;
4578       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4579       nrCastlingRights = 6;
4580         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4581         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4582         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4583         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4584         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4585         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4586       break;
4587     case VariantFalcon:
4588       pieces = FalconArray;
4589       gameInfo.boardWidth = 10;
4590       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4591       break;
4592     case VariantXiangqi:
4593       pieces = XiangqiArray;
4594       gameInfo.boardWidth  = 9;
4595       gameInfo.boardHeight = 10;
4596       nrCastlingRights = 0;
4597       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4598       break;
4599     case VariantShogi:
4600       pieces = ShogiArray;
4601       gameInfo.boardWidth  = 9;
4602       gameInfo.boardHeight = 9;
4603       gameInfo.holdingsSize = 7;
4604       nrCastlingRights = 0;
4605       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4606       break;
4607     case VariantCourier:
4608       pieces = CourierArray;
4609       gameInfo.boardWidth  = 12;
4610       nrCastlingRights = 0;
4611       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4612       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4613       break;
4614     case VariantKnightmate:
4615       pieces = KnightmateArray;
4616       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4617       break;
4618     case VariantFairy:
4619       pieces = fairyArray;
4620       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4621       break;
4622     case VariantGreat:
4623       pieces = GreatArray;
4624       gameInfo.boardWidth = 10;
4625       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4626       gameInfo.holdingsSize = 8;
4627       break;
4628     case VariantSuper:
4629       pieces = FIDEArray;
4630       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4631       gameInfo.holdingsSize = 8;
4632       startedFromSetupPosition = TRUE;
4633       break;
4634     case VariantCrazyhouse:
4635     case VariantBughouse:
4636       pieces = FIDEArray;
4637       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4638       gameInfo.holdingsSize = 5;
4639       break;
4640     case VariantWildCastle:
4641       pieces = FIDEArray;
4642       /* !!?shuffle with kings guaranteed to be on d or e file */
4643       shuffleOpenings = 1;
4644       break;
4645     case VariantNoCastle:
4646       pieces = FIDEArray;
4647       nrCastlingRights = 0;
4648       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4649       /* !!?unconstrained back-rank shuffle */
4650       shuffleOpenings = 1;
4651       break;
4652     }
4653
4654     overrule = 0;
4655     if(appData.NrFiles >= 0) {
4656         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4657         gameInfo.boardWidth = appData.NrFiles;
4658     }
4659     if(appData.NrRanks >= 0) {
4660         gameInfo.boardHeight = appData.NrRanks;
4661     }
4662     if(appData.holdingsSize >= 0) {
4663         i = appData.holdingsSize;
4664         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4665         gameInfo.holdingsSize = i;
4666     }
4667     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4668     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4669         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4670
4671     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4672     if(pawnRow < 1) pawnRow = 1;
4673
4674     /* User pieceToChar list overrules defaults */
4675     if(appData.pieceToCharTable != NULL)
4676         SetCharTable(pieceToChar, appData.pieceToCharTable);
4677
4678     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4679
4680         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4681             s = (ChessSquare) 0; /* account holding counts in guard band */
4682         for( i=0; i<BOARD_HEIGHT; i++ )
4683             initialPosition[i][j] = s;
4684
4685         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4686         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4687         initialPosition[pawnRow][j] = WhitePawn;
4688         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4689         if(gameInfo.variant == VariantXiangqi) {
4690             if(j&1) {
4691                 initialPosition[pawnRow][j] =
4692                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4693                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4694                    initialPosition[2][j] = WhiteCannon;
4695                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4696                 }
4697             }
4698         }
4699         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4700     }
4701     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4702
4703             j=BOARD_LEFT+1;
4704             initialPosition[1][j] = WhiteBishop;
4705             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4706             j=BOARD_RGHT-2;
4707             initialPosition[1][j] = WhiteRook;
4708             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4709     }
4710
4711     if( nrCastlingRights == -1) {
4712         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4713         /*       This sets default castling rights from none to normal corners   */
4714         /* Variants with other castling rights must set them themselves above    */
4715         nrCastlingRights = 6;
4716
4717         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4718         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4719         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4720         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4721         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4722         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4723      }
4724
4725      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4726      if(gameInfo.variant == VariantGreat) { // promotion commoners
4727         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4728         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4729         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4730         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4731      }
4732   if (appData.debugMode) {
4733     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4734   }
4735     if(shuffleOpenings) {
4736         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4737         startedFromSetupPosition = TRUE;
4738     }
4739     if(startedFromPositionFile) {
4740       /* [HGM] loadPos: use PositionFile for every new game */
4741       CopyBoard(initialPosition, filePosition);
4742       for(i=0; i<nrCastlingRights; i++)
4743           castlingRights[0][i] = initialRights[i] = fileRights[i];
4744       startedFromSetupPosition = TRUE;
4745     }
4746
4747     CopyBoard(boards[0], initialPosition);
4748     if(oldx != gameInfo.boardWidth ||
4749        oldy != gameInfo.boardHeight ||
4750        oldh != gameInfo.holdingsWidth
4751 #ifdef GOTHIC
4752        || oldv == VariantGothic ||        // For licensing popups
4753        gameInfo.variant == VariantGothic
4754 #endif
4755 #ifdef FALCON
4756        || oldv == VariantFalcon ||
4757        gameInfo.variant == VariantFalcon
4758 #endif
4759                                          )
4760       {
4761             InitDrawingSizes(-2 ,0);
4762       }
4763
4764     if (redraw)
4765       DrawPosition(TRUE, boards[currentMove]);
4766
4767 }
4768
4769 void
4770 SendBoard(cps, moveNum)
4771      ChessProgramState *cps;
4772      int moveNum;
4773 {
4774     char message[MSG_SIZ];
4775
4776     if (cps->useSetboard) {
4777       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4778       sprintf(message, "setboard %s\n", fen);
4779       SendToProgram(message, cps);
4780       free(fen);
4781
4782     } else {
4783       ChessSquare *bp;
4784       int i, j;
4785       /* Kludge to set black to move, avoiding the troublesome and now
4786        * deprecated "black" command.
4787        */
4788       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4789
4790       SendToProgram("edit\n", cps);
4791       SendToProgram("#\n", cps);
4792       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4793         bp = &boards[moveNum][i][BOARD_LEFT];
4794         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4795           if ((int) *bp < (int) BlackPawn) {
4796             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4797                     AAA + j, ONE + i);
4798             if(message[0] == '+' || message[0] == '~') {
4799                 sprintf(message, "%c%c%c+\n",
4800                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4801                         AAA + j, ONE + i);
4802             }
4803             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4804                 message[1] = BOARD_RGHT   - 1 - j + '1';
4805                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4806             }
4807             SendToProgram(message, cps);
4808           }
4809         }
4810       }
4811
4812       SendToProgram("c\n", cps);
4813       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4814         bp = &boards[moveNum][i][BOARD_LEFT];
4815         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4816           if (((int) *bp != (int) EmptySquare)
4817               && ((int) *bp >= (int) BlackPawn)) {
4818             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4819                     AAA + j, ONE + i);
4820             if(message[0] == '+' || message[0] == '~') {
4821                 sprintf(message, "%c%c%c+\n",
4822                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4823                         AAA + j, ONE + i);
4824             }
4825             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4826                 message[1] = BOARD_RGHT   - 1 - j + '1';
4827                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4828             }
4829             SendToProgram(message, cps);
4830           }
4831         }
4832       }
4833
4834       SendToProgram(".\n", cps);
4835     }
4836     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4837 }
4838
4839 int
4840 IsPromotion(fromX, fromY, toX, toY)
4841      int fromX, fromY, toX, toY;
4842 {
4843     /* [HGM] add Shogi promotions */
4844     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4845     ChessSquare piece;
4846
4847     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4848       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4849    /* [HGM] Note to self: line above also weeds out drops */
4850     piece = boards[currentMove][fromY][fromX];
4851     if(gameInfo.variant == VariantShogi) {
4852         promotionZoneSize = 3;
4853         highestPromotingPiece = (int)WhiteKing;
4854         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4855            and if in normal chess we then allow promotion to King, why not
4856            allow promotion of other piece in Shogi?                         */
4857     }
4858     if((int)piece >= BlackPawn) {
4859         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4860              return FALSE;
4861         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4862     } else {
4863         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4864            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4865     }
4866     return ( (int)piece <= highestPromotingPiece );
4867 }
4868
4869 int
4870 InPalace(row, column)
4871      int row, column;
4872 {   /* [HGM] for Xiangqi */
4873     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4874          column < (BOARD_WIDTH + 4)/2 &&
4875          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4876     return FALSE;
4877 }
4878
4879 int
4880 PieceForSquare (x, y)
4881      int x;
4882      int y;
4883 {
4884   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4885      return -1;
4886   else
4887      return boards[currentMove][y][x];
4888 }
4889
4890 int
4891 OKToStartUserMove(x, y)
4892      int x, y;
4893 {
4894     ChessSquare from_piece;
4895     int white_piece;
4896
4897     if (matchMode) return FALSE;
4898     if (gameMode == EditPosition) return TRUE;
4899
4900     if (x >= 0 && y >= 0)
4901       from_piece = boards[currentMove][y][x];
4902     else
4903       from_piece = EmptySquare;
4904
4905     if (from_piece == EmptySquare) return FALSE;
4906
4907     white_piece = (int)from_piece >= (int)WhitePawn &&
4908       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4909
4910     switch (gameMode) {
4911       case PlayFromGameFile:
4912       case AnalyzeFile:
4913       case TwoMachinesPlay:
4914       case EndOfGame:
4915         return FALSE;
4916
4917       case IcsObserving:
4918       case IcsIdle:
4919         return FALSE;
4920
4921       case MachinePlaysWhite:
4922       case IcsPlayingBlack:
4923         if (appData.zippyPlay) return FALSE;
4924         if (white_piece) {
4925             DisplayMoveError(_("You are playing Black"));
4926             return FALSE;
4927         }
4928         break;
4929
4930       case MachinePlaysBlack:
4931       case IcsPlayingWhite:
4932         if (appData.zippyPlay) return FALSE;
4933         if (!white_piece) {
4934             DisplayMoveError(_("You are playing White"));
4935             return FALSE;
4936         }
4937         break;
4938
4939       case EditGame:
4940         if (!white_piece && WhiteOnMove(currentMove)) {
4941             DisplayMoveError(_("It is White's turn"));
4942             return FALSE;
4943         }
4944         if (white_piece && !WhiteOnMove(currentMove)) {
4945             DisplayMoveError(_("It is Black's turn"));
4946             return FALSE;
4947         }
4948         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4949             /* Editing correspondence game history */
4950             /* Could disallow this or prompt for confirmation */
4951             cmailOldMove = -1;
4952         }
4953         if (currentMove < forwardMostMove) {
4954             /* Discarding moves */
4955             /* Could prompt for confirmation here,
4956                but I don't think that's such a good idea */
4957             forwardMostMove = currentMove;
4958         }
4959         break;
4960
4961       case BeginningOfGame:
4962         if (appData.icsActive) return FALSE;
4963         if (!appData.noChessProgram) {
4964             if (!white_piece) {
4965                 DisplayMoveError(_("You are playing White"));
4966                 return FALSE;
4967             }
4968         }
4969         break;
4970
4971       case Training:
4972         if (!white_piece && WhiteOnMove(currentMove)) {
4973             DisplayMoveError(_("It is White's turn"));
4974             return FALSE;
4975         }
4976         if (white_piece && !WhiteOnMove(currentMove)) {
4977             DisplayMoveError(_("It is Black's turn"));
4978             return FALSE;
4979         }
4980         break;
4981
4982       default:
4983       case IcsExamining:
4984         break;
4985     }
4986     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4987         && gameMode != AnalyzeFile && gameMode != Training) {
4988         DisplayMoveError(_("Displayed position is not current"));
4989         return FALSE;
4990     }
4991     return TRUE;
4992 }
4993
4994 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4995 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4996 int lastLoadGameUseList = FALSE;
4997 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4998 ChessMove lastLoadGameStart = (ChessMove) 0;
4999
5000 ChessMove
5001 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5002      int fromX, fromY, toX, toY;
5003      int promoChar;
5004      Boolean captureOwn;
5005 {
5006     ChessMove moveType;
5007     ChessSquare pdown, pup;
5008
5009     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5010
5011     /* [HGM] suppress all moves into holdings area and guard band */
5012     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5013             return ImpossibleMove;
5014
5015     /* [HGM] <sameColor> moved to here from winboard.c */
5016     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5017     pdown = boards[currentMove][fromY][fromX];
5018     pup = boards[currentMove][toY][toX];
5019     if (    gameMode != EditPosition && !captureOwn &&
5020             (WhitePawn <= pdown && pdown < BlackPawn &&
5021              WhitePawn <= pup && pup < BlackPawn  ||
5022              BlackPawn <= pdown && pdown < EmptySquare &&
5023              BlackPawn <= pup && pup < EmptySquare
5024             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5025                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5026                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5027                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5028                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5029         )           )
5030          return Comment;
5031
5032     /* Check if the user is playing in turn.  This is complicated because we
5033        let the user "pick up" a piece before it is his turn.  So the piece he
5034        tried to pick up may have been captured by the time he puts it down!
5035        Therefore we use the color the user is supposed to be playing in this
5036        test, not the color of the piece that is currently on the starting
5037        square---except in EditGame mode, where the user is playing both
5038        sides; fortunately there the capture race can't happen.  (It can
5039        now happen in IcsExamining mode, but that's just too bad.  The user
5040        will get a somewhat confusing message in that case.)
5041        */
5042
5043     switch (gameMode) {
5044       case PlayFromGameFile:
5045       case AnalyzeFile:
5046       case TwoMachinesPlay:
5047       case EndOfGame:
5048       case IcsObserving:
5049       case IcsIdle:
5050         /* We switched into a game mode where moves are not accepted,
5051            perhaps while the mouse button was down. */
5052         return ImpossibleMove;
5053
5054       case MachinePlaysWhite:
5055         /* User is moving for Black */
5056         if (WhiteOnMove(currentMove)) {
5057             DisplayMoveError(_("It is White's turn"));
5058             return ImpossibleMove;
5059         }
5060         break;
5061
5062       case MachinePlaysBlack:
5063         /* User is moving for White */
5064         if (!WhiteOnMove(currentMove)) {
5065             DisplayMoveError(_("It is Black's turn"));
5066             return ImpossibleMove;
5067         }
5068         break;
5069
5070       case EditGame:
5071       case IcsExamining:
5072       case BeginningOfGame:
5073       case AnalyzeMode:
5074       case Training:
5075         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5076             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5077             /* User is moving for Black */
5078             if (WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is White's turn"));
5080                 return ImpossibleMove;
5081             }
5082         } else {
5083             /* User is moving for White */
5084             if (!WhiteOnMove(currentMove)) {
5085                 DisplayMoveError(_("It is Black's turn"));
5086                 return ImpossibleMove;
5087             }
5088         }
5089         break;
5090
5091       case IcsPlayingBlack:
5092         /* User is moving for Black */
5093         if (WhiteOnMove(currentMove)) {
5094             if (!appData.premove) {
5095                 DisplayMoveError(_("It is White's turn"));
5096             } else if (toX >= 0 && toY >= 0) {
5097                 premoveToX = toX;
5098                 premoveToY = toY;
5099                 premoveFromX = fromX;
5100                 premoveFromY = fromY;
5101                 premovePromoChar = promoChar;
5102                 gotPremove = 1;
5103                 if (appData.debugMode)
5104                     fprintf(debugFP, "Got premove: fromX %d,"
5105                             "fromY %d, toX %d, toY %d\n",
5106                             fromX, fromY, toX, toY);
5107             }
5108             return ImpossibleMove;
5109         }
5110         break;
5111
5112       case IcsPlayingWhite:
5113         /* User is moving for White */
5114         if (!WhiteOnMove(currentMove)) {
5115             if (!appData.premove) {
5116                 DisplayMoveError(_("It is Black's turn"));
5117             } else if (toX >= 0 && toY >= 0) {
5118                 premoveToX = toX;
5119                 premoveToY = toY;
5120                 premoveFromX = fromX;
5121                 premoveFromY = fromY;
5122                 premovePromoChar = promoChar;
5123                 gotPremove = 1;
5124                 if (appData.debugMode)
5125                     fprintf(debugFP, "Got premove: fromX %d,"
5126                             "fromY %d, toX %d, toY %d\n",
5127                             fromX, fromY, toX, toY);
5128             }
5129             return ImpossibleMove;
5130         }
5131         break;
5132
5133       default:
5134         break;
5135
5136       case EditPosition:
5137         /* EditPosition, empty square, or different color piece;
5138            click-click move is possible */
5139         if (toX == -2 || toY == -2) {
5140             boards[0][fromY][fromX] = EmptySquare;
5141             return AmbiguousMove;
5142         } else if (toX >= 0 && toY >= 0) {
5143             boards[0][toY][toX] = boards[0][fromY][fromX];
5144             boards[0][fromY][fromX] = EmptySquare;
5145             return AmbiguousMove;
5146         }
5147         return ImpossibleMove;
5148     }
5149
5150     /* [HGM] If move started in holdings, it means a drop */
5151     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5152          if( pup != EmptySquare ) return ImpossibleMove;
5153          if(appData.testLegality) {
5154              /* it would be more logical if LegalityTest() also figured out
5155               * which drops are legal. For now we forbid pawns on back rank.
5156               * Shogi is on its own here...
5157               */
5158              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5159                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5160                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5161          }
5162          return WhiteDrop; /* Not needed to specify white or black yet */
5163     }
5164
5165     userOfferedDraw = FALSE;
5166
5167     /* [HGM] always test for legality, to get promotion info */
5168     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5169                           epStatus[currentMove], castlingRights[currentMove],
5170                                          fromY, fromX, toY, toX, promoChar);
5171     /* [HGM] but possibly ignore an IllegalMove result */
5172     if (appData.testLegality) {
5173         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5174             DisplayMoveError(_("Illegal move"));
5175             return ImpossibleMove;
5176         }
5177     }
5178     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5179     return moveType;
5180     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5181        function is made into one that returns an OK move type if FinishMove
5182        should be called. This to give the calling driver routine the
5183        opportunity to finish the userMove input with a promotion popup,
5184        without bothering the user with this for invalid or illegal moves */
5185
5186 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5187 }
5188
5189 /* Common tail of UserMoveEvent and DropMenuEvent */
5190 int
5191 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5192      ChessMove moveType;
5193      int fromX, fromY, toX, toY;
5194      /*char*/int promoChar;
5195 {
5196   char *bookHit = 0;
5197
5198   if(appData.debugMode)
5199     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5200
5201   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5202     {
5203       // [HGM] superchess: suppress promotions to non-available piece
5204       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5205       if(WhiteOnMove(currentMove))
5206         {
5207           if(!boards[currentMove][k][BOARD_WIDTH-2])
5208             return 0;
5209         }
5210       else
5211         {
5212           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5213             return 0;
5214         }
5215     }
5216   
5217   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5218      move type in caller when we know the move is a legal promotion */
5219   if(moveType == NormalMove && promoChar)
5220     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5221
5222   if(appData.debugMode) 
5223     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5224
5225   /* [HGM] convert drag-and-drop piece drops to standard form */
5226   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5227     {
5228       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5229       if(appData.debugMode) 
5230         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5231                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5232       //         fromX = boards[currentMove][fromY][fromX];
5233       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5234       if(fromX == 0) 
5235         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5236
5237       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5238
5239       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5240         fromX++; 
5241
5242       fromY = DROP_RANK;
5243     }
5244
5245   /* [HGM] <popupFix> The following if has been moved here from
5246      UserMoveEvent(). Because it seemed to belon here (why not allow
5247      piece drops in training games?), and because it can only be
5248      performed after it is known to what we promote. */
5249   if (gameMode == Training)
5250     {
5251       /* compare the move played on the board to the next move in the
5252        * game. If they match, display the move and the opponent's response.
5253        * If they don't match, display an error message.
5254        */
5255       int saveAnimate;
5256       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5257       CopyBoard(testBoard, boards[currentMove]);
5258       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5259
5260       if (CompareBoards(testBoard, boards[currentMove+1]))
5261         {
5262           ForwardInner(currentMove+1);
5263
5264           /* Autoplay the opponent's response.
5265            * if appData.animate was TRUE when Training mode was entered,
5266            * the response will be animated.
5267            */
5268           saveAnimate = appData.animate;
5269           appData.animate = animateTraining;
5270           ForwardInner(currentMove+1);
5271           appData.animate = saveAnimate;
5272
5273           /* check for the end of the game */
5274           if (currentMove >= forwardMostMove)
5275             {
5276               gameMode = PlayFromGameFile;
5277               ModeHighlight();
5278               SetTrainingModeOff();
5279               DisplayInformation(_("End of game"));
5280             }
5281         }
5282       else
5283         {
5284           DisplayError(_("Incorrect move"), 0);
5285         }
5286       return 1;
5287     }
5288
5289   /* Ok, now we know that the move is good, so we can kill
5290      the previous line in Analysis Mode */
5291   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5292     {
5293       forwardMostMove = currentMove;
5294     }
5295
5296   /* If we need the chess program but it's dead, restart it */
5297   ResurrectChessProgram();
5298
5299   /* A user move restarts a paused game*/
5300   if (pausing)
5301     PauseEvent();
5302
5303   thinkOutput[0] = NULLCHAR;
5304
5305   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5306
5307   if (gameMode == BeginningOfGame)
5308     {
5309       if (appData.noChessProgram)
5310         {
5311           gameMode = EditGame;
5312           SetGameInfo();
5313         }
5314       else
5315         {
5316           char buf[MSG_SIZ];
5317           gameMode = MachinePlaysBlack;
5318           StartClocks();
5319           SetGameInfo();
5320           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5321           DisplayTitle(buf);
5322           if (first.sendName)
5323             {
5324               sprintf(buf, "name %s\n", gameInfo.white);
5325               SendToProgram(buf, &first);
5326             }
5327           StartClocks();
5328         }
5329       ModeHighlight();
5330     }
5331   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5332
5333   /* Relay move to ICS or chess engine */
5334   if (appData.icsActive)
5335     {
5336       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5337           gameMode == IcsExamining)
5338         {
5339           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5340           ics_user_moved = 1;
5341         }
5342     }
5343   else
5344     {
5345       if (first.sendTime && (gameMode == BeginningOfGame ||
5346                              gameMode == MachinePlaysWhite ||
5347                              gameMode == MachinePlaysBlack))
5348         {
5349           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5350         }
5351       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5352         {
5353           // [HGM] book: if program might be playing, let it use book
5354           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5355           first.maybeThinking = TRUE;
5356         }
5357       else
5358         SendMoveToProgram(forwardMostMove-1, &first);
5359       if (currentMove == cmailOldMove + 1)
5360         {
5361           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5362         }
5363     }
5364
5365   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5366
5367   switch (gameMode)
5368     {
5369     case EditGame:
5370       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5371                        EP_UNKNOWN, castlingRights[currentMove]) )
5372         {
5373         case MT_NONE:
5374         case MT_CHECK:
5375           break;
5376         case MT_CHECKMATE:
5377         case MT_STAINMATE:
5378           if (WhiteOnMove(currentMove))
5379             {
5380               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5381             }
5382           else
5383             {
5384               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5385             }
5386           break;
5387         case MT_STALEMATE:
5388           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5389           break;
5390     }
5391       break;
5392
5393     case MachinePlaysBlack:
5394     case MachinePlaysWhite:
5395       /* disable certain menu options while machine is thinking */
5396       SetMachineThinkingEnables();
5397       break;
5398
5399     default:
5400       break;
5401     }
5402
5403   if(bookHit)
5404     { // [HGM] book: simulate book reply
5405       static char bookMove[MSG_SIZ]; // a bit generous?
5406
5407       programStats.nodes = programStats.depth = programStats.time =
5408         programStats.score = programStats.got_only_move = 0;
5409       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5410
5411       strcpy(bookMove, "move ");
5412       strcat(bookMove, bookHit);
5413       HandleMachineMove(bookMove, &first);
5414     }
5415
5416   return 1;
5417 }
5418
5419 void
5420 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5421      int fromX, fromY, toX, toY;
5422      int promoChar;
5423 {
5424     /* [HGM] This routine was added to allow calling of its two logical
5425        parts from other modules in the old way. Before, UserMoveEvent()
5426        automatically called FinishMove() if the move was OK, and returned
5427        otherwise. I separated the two, in order to make it possible to
5428        slip a promotion popup in between. But that it always needs two
5429        calls, to the first part, (now called UserMoveTest() ), and to
5430        FinishMove if the first part succeeded. Calls that do not need
5431        to do anything in between, can call this routine the old way.
5432     */
5433     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5434 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5435     if(moveType == AmbiguousMove)
5436         DrawPosition(FALSE, boards[currentMove]);
5437     else if(moveType != ImpossibleMove && moveType != Comment)
5438         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5439 }
5440
5441 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5442 {
5443 //    char * hint = lastHint;
5444     FrontEndProgramStats stats;
5445
5446     stats.which = cps == &first ? 0 : 1;
5447     stats.depth = cpstats->depth;
5448     stats.nodes = cpstats->nodes;
5449     stats.score = cpstats->score;
5450     stats.time = cpstats->time;
5451     stats.pv = cpstats->movelist;
5452     stats.hint = lastHint;
5453     stats.an_move_index = 0;
5454     stats.an_move_count = 0;
5455
5456     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5457         stats.hint = cpstats->move_name;
5458         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5459         stats.an_move_count = cpstats->nr_moves;
5460     }
5461
5462     SetProgramStats( &stats );
5463 }
5464
5465 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5466 {   // [HGM] book: this routine intercepts moves to simulate book replies
5467     char *bookHit = NULL;
5468
5469     //first determine if the incoming move brings opponent into his book
5470     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5471         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5472     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5473     if(bookHit != NULL && !cps->bookSuspend) {
5474         // make sure opponent is not going to reply after receiving move to book position
5475         SendToProgram("force\n", cps);
5476         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5477     }
5478     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5479     // now arrange restart after book miss
5480     if(bookHit) {
5481         // after a book hit we never send 'go', and the code after the call to this routine
5482         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5483         char buf[MSG_SIZ];
5484         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5485         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5486         SendToProgram(buf, cps);
5487         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5488     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5489         SendToProgram("go\n", cps);
5490         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5491     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5492         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5493             SendToProgram("go\n", cps);
5494         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5495     }
5496     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5497 }
5498
5499 char *savedMessage;
5500 ChessProgramState *savedState;
5501 void DeferredBookMove(void)
5502 {
5503         if(savedState->lastPing != savedState->lastPong)
5504                     ScheduleDelayedEvent(DeferredBookMove, 10);
5505         else
5506         HandleMachineMove(savedMessage, savedState);
5507 }
5508
5509 void
5510 HandleMachineMove(message, cps)
5511      char *message;
5512      ChessProgramState *cps;
5513 {
5514     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5515     char realname[MSG_SIZ];
5516     int fromX, fromY, toX, toY;
5517     ChessMove moveType;
5518     char promoChar;
5519     char *p;
5520     int machineWhite;
5521     char *bookHit;
5522
5523 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5524     /*
5525      * Kludge to ignore BEL characters
5526      */
5527     while (*message == '\007') message++;
5528
5529     /*
5530      * [HGM] engine debug message: ignore lines starting with '#' character
5531      */
5532     if(cps->debug && *message == '#') return;
5533
5534     /*
5535      * Look for book output
5536      */
5537     if (cps == &first && bookRequested) {
5538         if (message[0] == '\t' || message[0] == ' ') {
5539             /* Part of the book output is here; append it */
5540             strcat(bookOutput, message);
5541             strcat(bookOutput, "  \n");
5542             return;
5543         } else if (bookOutput[0] != NULLCHAR) {
5544             /* All of book output has arrived; display it */
5545             char *p = bookOutput;
5546             while (*p != NULLCHAR) {
5547                 if (*p == '\t') *p = ' ';
5548                 p++;
5549             }
5550             DisplayInformation(bookOutput);
5551             bookRequested = FALSE;
5552             /* Fall through to parse the current output */
5553         }
5554     }
5555
5556     /*
5557      * Look for machine move.
5558      */
5559     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5560         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5561     {
5562         /* This method is only useful on engines that support ping */
5563         if (cps->lastPing != cps->lastPong) {
5564           if (gameMode == BeginningOfGame) {
5565             /* Extra move from before last new; ignore */
5566             if (appData.debugMode) {
5567                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5568             }
5569           } else {
5570             if (appData.debugMode) {
5571                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5572                         cps->which, gameMode);
5573             }
5574
5575             SendToProgram("undo\n", cps);
5576           }
5577           return;
5578         }
5579
5580         switch (gameMode) {
5581           case BeginningOfGame:
5582             /* Extra move from before last reset; ignore */
5583             if (appData.debugMode) {
5584                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5585             }
5586             return;
5587
5588           case EndOfGame:
5589           case IcsIdle:
5590           default:
5591             /* Extra move after we tried to stop.  The mode test is
5592                not a reliable way of detecting this problem, but it's
5593                the best we can do on engines that don't support ping.
5594             */
5595             if (appData.debugMode) {
5596                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5597                         cps->which, gameMode);
5598             }
5599             SendToProgram("undo\n", cps);
5600             return;
5601
5602           case MachinePlaysWhite:
5603           case IcsPlayingWhite:
5604             machineWhite = TRUE;
5605             break;
5606
5607           case MachinePlaysBlack:
5608           case IcsPlayingBlack:
5609             machineWhite = FALSE;
5610             break;
5611
5612           case TwoMachinesPlay:
5613             machineWhite = (cps->twoMachinesColor[0] == 'w');
5614             break;
5615         }
5616         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5617             if (appData.debugMode) {
5618                 fprintf(debugFP,
5619                         "Ignoring move out of turn by %s, gameMode %d"
5620                         ", forwardMost %d\n",
5621                         cps->which, gameMode, forwardMostMove);
5622             }
5623             return;
5624         }
5625
5626     if (appData.debugMode) { int f = forwardMostMove;
5627         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5628                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5629     }
5630         if(cps->alphaRank) AlphaRank(machineMove, 4);
5631         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5632                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5633             /* Machine move could not be parsed; ignore it. */
5634             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5635                     machineMove, cps->which);
5636             DisplayError(buf1, 0);
5637             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5638                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5639             if (gameMode == TwoMachinesPlay) {
5640               GameEnds(machineWhite ? BlackWins : WhiteWins,
5641                        buf1, GE_XBOARD);
5642             }
5643             return;
5644         }
5645
5646         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5647         /* So we have to redo legality test with true e.p. status here,  */
5648         /* to make sure an illegal e.p. capture does not slip through,   */
5649         /* to cause a forfeit on a justified illegal-move complaint      */
5650         /* of the opponent.                                              */
5651         if( gameMode==TwoMachinesPlay && appData.testLegality
5652             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5653                                                               ) {
5654            ChessMove moveType;
5655            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5656                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5657                              fromY, fromX, toY, toX, promoChar);
5658             if (appData.debugMode) {
5659                 int i;
5660                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5661                     castlingRights[forwardMostMove][i], castlingRank[i]);
5662                 fprintf(debugFP, "castling rights\n");
5663             }
5664             if(moveType == IllegalMove) {
5665                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5666                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5667                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5668                            buf1, GE_XBOARD);
5669                 return;
5670            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5671            /* [HGM] Kludge to handle engines that send FRC-style castling
5672               when they shouldn't (like TSCP-Gothic) */
5673            switch(moveType) {
5674              case WhiteASideCastleFR:
5675              case BlackASideCastleFR:
5676                toX+=2;
5677                currentMoveString[2]++;
5678                break;
5679              case WhiteHSideCastleFR:
5680              case BlackHSideCastleFR:
5681                toX--;
5682                currentMoveString[2]--;
5683                break;
5684              default: ; // nothing to do, but suppresses warning of pedantic compilers
5685            }
5686         }
5687         hintRequested = FALSE;
5688         lastHint[0] = NULLCHAR;
5689         bookRequested = FALSE;
5690         /* Program may be pondering now */
5691         cps->maybeThinking = TRUE;
5692         if (cps->sendTime == 2) cps->sendTime = 1;
5693         if (cps->offeredDraw) cps->offeredDraw--;
5694
5695 #if ZIPPY
5696         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5697             first.initDone) {
5698           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5699           ics_user_moved = 1;
5700           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5701                 char buf[3*MSG_SIZ];
5702
5703                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5704                         programStats.score / 100.,
5705                         programStats.depth,
5706                         programStats.time / 100.,
5707                         (unsigned int)programStats.nodes,
5708                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5709                         programStats.movelist);
5710                 SendToICS(buf);
5711 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5712           }
5713         }
5714 #endif
5715         /* currentMoveString is set as a side-effect of ParseOneMove */
5716         strcpy(machineMove, currentMoveString);
5717         strcat(machineMove, "\n");
5718         strcpy(moveList[forwardMostMove], machineMove);
5719
5720         /* [AS] Save move info and clear stats for next move */
5721         pvInfoList[ forwardMostMove ].score = programStats.score;
5722         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5723         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5724         ClearProgramStats();
5725         thinkOutput[0] = NULLCHAR;
5726         hiddenThinkOutputState = 0;
5727
5728         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5729
5730         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5731         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5732             int count = 0;
5733
5734             while( count < adjudicateLossPlies ) {
5735                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5736
5737                 if( count & 1 ) {
5738                     score = -score; /* Flip score for winning side */
5739                 }
5740
5741                 if( score > adjudicateLossThreshold ) {
5742                     break;
5743                 }
5744
5745                 count++;
5746             }
5747
5748             if( count >= adjudicateLossPlies ) {
5749                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5750
5751                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5752                     "Xboard adjudication",
5753                     GE_XBOARD );
5754
5755                 return;
5756             }
5757         }
5758
5759         if( gameMode == TwoMachinesPlay ) {
5760           // [HGM] some adjudications useful with buggy engines
5761             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5762           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5763
5764
5765             if( appData.testLegality )
5766             {   /* [HGM] Some more adjudications for obstinate engines */
5767                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5768                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5769                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5770                 static int moveCount = 6;
5771                 ChessMove result;
5772                 char *reason = NULL;
5773
5774                 /* Count what is on board. */
5775                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5776                 {   ChessSquare p = boards[forwardMostMove][i][j];
5777                     int m=i;
5778
5779                     switch((int) p)
5780                     {   /* count B,N,R and other of each side */
5781                         case WhiteKing:
5782                         case BlackKing:
5783                              NrK++; break; // [HGM] atomic: count Kings
5784                         case WhiteKnight:
5785                              NrWN++; break;
5786                         case WhiteBishop:
5787                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5788                              bishopsColor |= 1 << ((i^j)&1);
5789                              NrWB++; break;
5790                         case BlackKnight:
5791                              NrBN++; break;
5792                         case BlackBishop:
5793                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5794                              bishopsColor |= 1 << ((i^j)&1);
5795                              NrBB++; break;
5796                         case WhiteRook:
5797                              NrWR++; break;
5798                         case BlackRook:
5799                              NrBR++; break;
5800                         case WhiteQueen:
5801                              NrWQ++; break;
5802                         case BlackQueen:
5803                              NrBQ++; break;
5804                         case EmptySquare:
5805                              break;
5806                         case BlackPawn:
5807                              m = 7-i;
5808                         case WhitePawn:
5809                              PawnAdvance += m; NrPawns++;
5810                     }
5811                     NrPieces += (p != EmptySquare);
5812                     NrW += ((int)p < (int)BlackPawn);
5813                     if(gameInfo.variant == VariantXiangqi &&
5814                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5815                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5816                         NrW -= ((int)p < (int)BlackPawn);
5817                     }
5818                 }
5819
5820                 /* Some material-based adjudications that have to be made before stalemate test */
5821                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5822                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5823                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5824                      if(appData.checkMates) {
5825                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5826                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5827                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5828                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5829                          return;
5830                      }
5831                 }
5832
5833                 /* Bare King in Shatranj (loses) or Losers (wins) */
5834                 if( NrW == 1 || NrPieces - NrW == 1) {
5835                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5836                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5837                      if(appData.checkMates) {
5838                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5839                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5840                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5841                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5842                          return;
5843                      }
5844                   } else
5845                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5846                   {    /* bare King */
5847                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5848                         if(appData.checkMates) {
5849                             /* but only adjudicate if adjudication enabled */
5850                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5851                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5852                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5853                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5854                             return;
5855                         }
5856                   }
5857                 } else bare = 1;
5858
5859
5860             // don't wait for engine to announce game end if we can judge ourselves
5861             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5862                                        castlingRights[forwardMostMove]) ) {
5863               case MT_CHECK:
5864                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5865                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5866                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5867                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5868                             checkCnt++;
5869                         if(checkCnt >= 2) {
5870                             reason = "Xboard adjudication: 3rd check";
5871                             epStatus[forwardMostMove] = EP_CHECKMATE;
5872                             break;
5873                         }
5874                     }
5875                 }
5876               case MT_NONE:
5877               default:
5878                 break;
5879               case MT_STALEMATE:
5880               case MT_STAINMATE:
5881                 reason = "Xboard adjudication: Stalemate";
5882                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5883                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5884                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5885                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5886                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5887                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5888                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5889                                                                         EP_CHECKMATE : EP_WINS);
5890                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5891                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5892                 }
5893                 break;
5894               case MT_CHECKMATE:
5895                 reason = "Xboard adjudication: Checkmate";
5896                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5897                 break;
5898             }
5899
5900                 switch(i = epStatus[forwardMostMove]) {
5901                     case EP_STALEMATE:
5902                         result = GameIsDrawn; break;
5903                     case EP_CHECKMATE:
5904                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5905                     case EP_WINS:
5906                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5907                     default:
5908                         result = (ChessMove) 0;
5909                 }
5910                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5911                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5912                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5913                     GameEnds( result, reason, GE_XBOARD );
5914                     return;
5915                 }
5916
5917                 /* Next absolutely insufficient mating material. */
5918                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5919                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5920                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5921                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5922                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5923
5924                      /* always flag draws, for judging claims */
5925                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5926
5927                      if(appData.materialDraws) {
5928                          /* but only adjudicate them if adjudication enabled */
5929                          SendToProgram("force\n", cps->other); // suppress reply
5930                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5931                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5932                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5933                          return;
5934                      }
5935                 }
5936
5937                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5938                 if(NrPieces == 4 &&
5939                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5940                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5941                    || NrWN==2 || NrBN==2     /* KNNK */
5942                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5943                   ) ) {
5944                      if(--moveCount < 0 && appData.trivialDraws)
5945                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5946                           SendToProgram("force\n", cps->other); // suppress reply
5947                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5948                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5949                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5950                           return;
5951                      }
5952                 } else moveCount = 6;
5953             }
5954           }
5955           
5956           if (appData.debugMode) { int i;
5957             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5958                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5959                     appData.drawRepeats);
5960             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5961               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5962             
5963           }
5964
5965                 /* Check for rep-draws */
5966                 count = 0;
5967                 for(k = forwardMostMove-2;
5968                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5969                         epStatus[k] < EP_UNKNOWN &&
5970                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5971                     k-=2)
5972                 {   int rights=0;
5973                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5974                         /* compare castling rights */
5975                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5976                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5977                                 rights++; /* King lost rights, while rook still had them */
5978                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5979                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5980                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5981                                    rights++; /* but at least one rook lost them */
5982                         }
5983                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5984                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5985                                 rights++;
5986                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5987                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5988                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5989                                    rights++;
5990                         }
5991                         if( rights == 0 && ++count > appData.drawRepeats-2
5992                             && appData.drawRepeats > 1) {
5993                              /* adjudicate after user-specified nr of repeats */
5994                              SendToProgram("force\n", cps->other); // suppress reply
5995                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5996                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5997                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5998                                 // [HGM] xiangqi: check for forbidden perpetuals
5999                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6000                                 for(m=forwardMostMove; m>k; m-=2) {
6001                                     if(MateTest(boards[m], PosFlags(m),
6002                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6003                                         ourPerpetual = 0; // the current mover did not always check
6004                                     if(MateTest(boards[m-1], PosFlags(m-1),
6005                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6006                                         hisPerpetual = 0; // the opponent did not always check
6007                                 }
6008                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6009                                                                         ourPerpetual, hisPerpetual);
6010                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6011                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6012                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6013                                     return;
6014                                 }
6015                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6016                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6017                                 // Now check for perpetual chases
6018                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6019                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6020                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6021                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6022                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6023                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6024                                         return;
6025                                     }
6026                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6027                                         break; // Abort repetition-checking loop.
6028                                 }
6029                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6030                              }
6031                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6032                              return;
6033                         }
6034                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6035                              epStatus[forwardMostMove] = EP_REP_DRAW;
6036                     }
6037                 }
6038
6039                 /* Now we test for 50-move draws. Determine ply count */
6040                 count = forwardMostMove;
6041                 /* look for last irreversble move */
6042                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6043                     count--;
6044                 /* if we hit starting position, add initial plies */
6045                 if( count == backwardMostMove )
6046                     count -= initialRulePlies;
6047                 count = forwardMostMove - count;
6048                 if( count >= 100)
6049                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6050                          /* this is used to judge if draw claims are legal */
6051                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6052                          SendToProgram("force\n", cps->other); // suppress reply
6053                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6054                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6055                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6056                          return;
6057                 }
6058
6059                 /* if draw offer is pending, treat it as a draw claim
6060                  * when draw condition present, to allow engines a way to
6061                  * claim draws before making their move to avoid a race
6062                  * condition occurring after their move
6063                  */
6064                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6065                          char *p = NULL;
6066                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6067                              p = "Draw claim: 50-move rule";
6068                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6069                              p = "Draw claim: 3-fold repetition";
6070                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6071                              p = "Draw claim: insufficient mating material";
6072                          if( p != NULL ) {
6073                              SendToProgram("force\n", cps->other); // suppress reply
6074                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6075                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6076                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6077                              return;
6078                          }
6079                 }
6080
6081
6082                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6083                     SendToProgram("force\n", cps->other); // suppress reply
6084                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6085                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6086
6087                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6088
6089                     return;
6090                 }
6091         }
6092
6093         bookHit = NULL;
6094         if (gameMode == TwoMachinesPlay) {
6095             /* [HGM] relaying draw offers moved to after reception of move */
6096             /* and interpreting offer as claim if it brings draw condition */
6097             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6098                 SendToProgram("draw\n", cps->other);
6099             }
6100             if (cps->other->sendTime) {
6101                 SendTimeRemaining(cps->other,
6102                                   cps->other->twoMachinesColor[0] == 'w');
6103             }
6104             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6105             if (firstMove && !bookHit) {
6106                 firstMove = FALSE;
6107                 if (cps->other->useColors) {
6108                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6109                 }
6110                 SendToProgram("go\n", cps->other);
6111             }
6112             cps->other->maybeThinking = TRUE;
6113         }
6114
6115         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116
6117         if (!pausing && appData.ringBellAfterMoves) {
6118             RingBell();
6119         }
6120
6121         /*
6122          * Reenable menu items that were disabled while
6123          * machine was thinking
6124          */
6125         if (gameMode != TwoMachinesPlay)
6126             SetUserThinkingEnables();
6127
6128         // [HGM] book: after book hit opponent has received move and is now in force mode
6129         // force the book reply into it, and then fake that it outputted this move by jumping
6130         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6131         if(bookHit) {
6132                 static char bookMove[MSG_SIZ]; // a bit generous?
6133
6134                 strcpy(bookMove, "move ");
6135                 strcat(bookMove, bookHit);
6136                 message = bookMove;
6137                 cps = cps->other;
6138                 programStats.nodes = programStats.depth = programStats.time =
6139                 programStats.score = programStats.got_only_move = 0;
6140                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6141
6142                 if(cps->lastPing != cps->lastPong) {
6143                     savedMessage = message; // args for deferred call
6144                     savedState = cps;
6145                     ScheduleDelayedEvent(DeferredBookMove, 10);
6146                     return;
6147                 }
6148                 goto FakeBookMove;
6149         }
6150
6151         return;
6152     }
6153
6154     /* Set special modes for chess engines.  Later something general
6155      *  could be added here; for now there is just one kludge feature,
6156      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6157      *  when "xboard" is given as an interactive command.
6158      */
6159     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6160         cps->useSigint = FALSE;
6161         cps->useSigterm = FALSE;
6162     }
6163     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6164       ParseFeatures(message+8, cps);
6165       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6166     }
6167
6168     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6169      * want this, I was asked to put it in, and obliged.
6170      */
6171     if (!strncmp(message, "setboard ", 9)) {
6172         Board initial_position; int i;
6173
6174         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6175
6176         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6177             DisplayError(_("Bad FEN received from engine"), 0);
6178             return ;
6179         } else {
6180            Reset(FALSE, FALSE);
6181            CopyBoard(boards[0], initial_position);
6182            initialRulePlies = FENrulePlies;
6183            epStatus[0] = FENepStatus;
6184            for( i=0; i<nrCastlingRights; i++ )
6185                 castlingRights[0][i] = FENcastlingRights[i];
6186            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6187            else gameMode = MachinePlaysBlack;
6188            DrawPosition(FALSE, boards[currentMove]);
6189         }
6190         return;
6191     }
6192
6193     /*
6194      * Look for communication commands
6195      */
6196     if (!strncmp(message, "telluser ", 9)) {
6197         DisplayNote(message + 9);
6198         return;
6199     }
6200     if (!strncmp(message, "tellusererror ", 14)) {
6201         DisplayError(message + 14, 0);
6202         return;
6203     }
6204     if (!strncmp(message, "tellopponent ", 13)) {
6205       if (appData.icsActive) {
6206         if (loggedOn) {
6207           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6208           SendToICS(buf1);
6209         }
6210       } else {
6211         DisplayNote(message + 13);
6212       }
6213       return;
6214     }
6215     if (!strncmp(message, "tellothers ", 11)) {
6216       if (appData.icsActive) {
6217         if (loggedOn) {
6218           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6219           SendToICS(buf1);
6220         }
6221       }
6222       return;
6223     }
6224     if (!strncmp(message, "tellall ", 8)) {
6225       if (appData.icsActive) {
6226         if (loggedOn) {
6227           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6228           SendToICS(buf1);
6229         }
6230       } else {
6231         DisplayNote(message + 8);
6232       }
6233       return;
6234     }
6235     if (strncmp(message, "warning", 7) == 0) {
6236         /* Undocumented feature, use tellusererror in new code */
6237         DisplayError(message, 0);
6238         return;
6239     }
6240     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6241         strcpy(realname, cps->tidy);
6242         strcat(realname, " query");
6243         AskQuestion(realname, buf2, buf1, cps->pr);
6244         return;
6245     }
6246     /* Commands from the engine directly to ICS.  We don't allow these to be
6247      *  sent until we are logged on. Crafty kibitzes have been known to
6248      *  interfere with the login process.
6249      */
6250     if (loggedOn) {
6251         if (!strncmp(message, "tellics ", 8)) {
6252             SendToICS(message + 8);
6253             SendToICS("\n");
6254             return;
6255         }
6256         if (!strncmp(message, "tellicsnoalias ", 15)) {
6257             SendToICS(ics_prefix);
6258             SendToICS(message + 15);
6259             SendToICS("\n");
6260             return;
6261         }
6262         /* The following are for backward compatibility only */
6263         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6264             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6265             SendToICS(ics_prefix);
6266             SendToICS(message);
6267             SendToICS("\n");
6268             return;
6269         }
6270     }
6271     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6272         return;
6273     }
6274     /*
6275      * If the move is illegal, cancel it and redraw the board.
6276      * Also deal with other error cases.  Matching is rather loose
6277      * here to accommodate engines written before the spec.
6278      */
6279     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6280         strncmp(message, "Error", 5) == 0) {
6281         if (StrStr(message, "name") ||
6282             StrStr(message, "rating") || StrStr(message, "?") ||
6283             StrStr(message, "result") || StrStr(message, "board") ||
6284             StrStr(message, "bk") || StrStr(message, "computer") ||
6285             StrStr(message, "variant") || StrStr(message, "hint") ||
6286             StrStr(message, "random") || StrStr(message, "depth") ||
6287             StrStr(message, "accepted")) {
6288             return;
6289         }
6290         if (StrStr(message, "protover")) {
6291           /* Program is responding to input, so it's apparently done
6292              initializing, and this error message indicates it is
6293              protocol version 1.  So we don't need to wait any longer
6294              for it to initialize and send feature commands. */
6295           FeatureDone(cps, 1);
6296           cps->protocolVersion = 1;
6297           return;
6298         }
6299         cps->maybeThinking = FALSE;
6300
6301         if (StrStr(message, "draw")) {
6302             /* Program doesn't have "draw" command */
6303             cps->sendDrawOffers = 0;
6304             return;
6305         }
6306         if (cps->sendTime != 1 &&
6307             (StrStr(message, "time") || StrStr(message, "otim"))) {
6308           /* Program apparently doesn't have "time" or "otim" command */
6309           cps->sendTime = 0;
6310           return;
6311         }
6312         if (StrStr(message, "analyze")) {
6313             cps->analysisSupport = FALSE;
6314             cps->analyzing = FALSE;
6315             Reset(FALSE, TRUE);
6316             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6317             DisplayError(buf2, 0);
6318             return;
6319         }
6320         if (StrStr(message, "(no matching move)st")) {
6321           /* Special kludge for GNU Chess 4 only */
6322           cps->stKludge = TRUE;
6323           SendTimeControl(cps, movesPerSession, timeControl,
6324                           timeIncrement, appData.searchDepth,
6325                           searchTime);
6326           return;
6327         }
6328         if (StrStr(message, "(no matching move)sd")) {
6329           /* Special kludge for GNU Chess 4 only */
6330           cps->sdKludge = TRUE;
6331           SendTimeControl(cps, movesPerSession, timeControl,
6332                           timeIncrement, appData.searchDepth,
6333                           searchTime);
6334           return;
6335         }
6336         if (!StrStr(message, "llegal")) {
6337             return;
6338         }
6339         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6340             gameMode == IcsIdle) return;
6341         if (forwardMostMove <= backwardMostMove) return;
6342         if (pausing) PauseEvent();
6343       if(appData.forceIllegal) {
6344             // [HGM] illegal: machine refused move; force position after move into it
6345           SendToProgram("force\n", cps);
6346           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6347                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6348                 // when black is to move, while there might be nothing on a2 or black
6349                 // might already have the move. So send the board as if white has the move.
6350                 // But first we must change the stm of the engine, as it refused the last move
6351                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6352                 if(WhiteOnMove(forwardMostMove)) {
6353                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6354                     SendBoard(cps, forwardMostMove); // kludgeless board
6355                 } else {
6356                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6357                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6358                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6359                 }
6360           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6361             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6362                  gameMode == TwoMachinesPlay)
6363               SendToProgram("go\n", cps);
6364             return;
6365       } else
6366         if (gameMode == PlayFromGameFile) {
6367             /* Stop reading this game file */
6368             gameMode = EditGame;
6369             ModeHighlight();
6370         }
6371         currentMove = --forwardMostMove;
6372         DisplayMove(currentMove-1); /* before DisplayMoveError */
6373         SwitchClocks();
6374         DisplayBothClocks();
6375         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6376                 parseList[currentMove], cps->which);
6377         DisplayMoveError(buf1);
6378         DrawPosition(FALSE, boards[currentMove]);
6379
6380         /* [HGM] illegal-move claim should forfeit game when Xboard */
6381         /* only passes fully legal moves                            */
6382         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6383             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6384                                 "False illegal-move claim", GE_XBOARD );
6385         }
6386         return;
6387     }
6388     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6389         /* Program has a broken "time" command that
6390            outputs a string not ending in newline.
6391            Don't use it. */
6392         cps->sendTime = 0;
6393     }
6394
6395     /*
6396      * If chess program startup fails, exit with an error message.
6397      * Attempts to recover here are futile.
6398      */
6399     if ((StrStr(message, "unknown host") != NULL)
6400         || (StrStr(message, "No remote directory") != NULL)
6401         || (StrStr(message, "not found") != NULL)
6402         || (StrStr(message, "No such file") != NULL)
6403         || (StrStr(message, "can't alloc") != NULL)
6404         || (StrStr(message, "Permission denied") != NULL)) {
6405
6406         cps->maybeThinking = FALSE;
6407         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6408                 cps->which, cps->program, cps->host, message);
6409         RemoveInputSource(cps->isr);
6410         DisplayFatalError(buf1, 0, 1);
6411         return;
6412     }
6413
6414     /*
6415      * Look for hint output
6416      */
6417     if (sscanf(message, "Hint: %s", buf1) == 1) {
6418         if (cps == &first && hintRequested) {
6419             hintRequested = FALSE;
6420             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6421                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6422                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6423                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6424                                     fromY, fromX, toY, toX, promoChar, buf1);
6425                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6426                 DisplayInformation(buf2);
6427             } else {
6428                 /* Hint move could not be parsed!? */
6429               snprintf(buf2, sizeof(buf2),
6430                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6431                         buf1, cps->which);
6432                 DisplayError(buf2, 0);
6433             }
6434         } else {
6435             strcpy(lastHint, buf1);
6436         }
6437         return;
6438     }
6439
6440     /*
6441      * Ignore other messages if game is not in progress
6442      */
6443     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6444         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6445
6446     /*
6447      * look for win, lose, draw, or draw offer
6448      */
6449     if (strncmp(message, "1-0", 3) == 0) {
6450         char *p, *q, *r = "";
6451         p = strchr(message, '{');
6452         if (p) {
6453             q = strchr(p, '}');
6454             if (q) {
6455                 *q = NULLCHAR;
6456                 r = p + 1;
6457             }
6458         }
6459         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6460         return;
6461     } else if (strncmp(message, "0-1", 3) == 0) {
6462         char *p, *q, *r = "";
6463         p = strchr(message, '{');
6464         if (p) {
6465             q = strchr(p, '}');
6466             if (q) {
6467                 *q = NULLCHAR;
6468                 r = p + 1;
6469             }
6470         }
6471         /* Kludge for Arasan 4.1 bug */
6472         if (strcmp(r, "Black resigns") == 0) {
6473             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6474             return;
6475         }
6476         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6477         return;
6478     } else if (strncmp(message, "1/2", 3) == 0) {
6479         char *p, *q, *r = "";
6480         p = strchr(message, '{');
6481         if (p) {
6482             q = strchr(p, '}');
6483             if (q) {
6484                 *q = NULLCHAR;
6485                 r = p + 1;
6486             }
6487         }
6488
6489         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6490         return;
6491
6492     } else if (strncmp(message, "White resign", 12) == 0) {
6493         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6494         return;
6495     } else if (strncmp(message, "Black resign", 12) == 0) {
6496         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6497         return;
6498     } else if (strncmp(message, "White matches", 13) == 0 ||
6499                strncmp(message, "Black matches", 13) == 0   ) {
6500         /* [HGM] ignore GNUShogi noises */
6501         return;
6502     } else if (strncmp(message, "White", 5) == 0 &&
6503                message[5] != '(' &&
6504                StrStr(message, "Black") == NULL) {
6505         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6506         return;
6507     } else if (strncmp(message, "Black", 5) == 0 &&
6508                message[5] != '(') {
6509         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6510         return;
6511     } else if (strcmp(message, "resign") == 0 ||
6512                strcmp(message, "computer resigns") == 0) {
6513         switch (gameMode) {
6514           case MachinePlaysBlack:
6515           case IcsPlayingBlack:
6516             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6517             break;
6518           case MachinePlaysWhite:
6519           case IcsPlayingWhite:
6520             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6521             break;
6522           case TwoMachinesPlay:
6523             if (cps->twoMachinesColor[0] == 'w')
6524               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6525             else
6526               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6527             break;
6528           default:
6529             /* can't happen */
6530             break;
6531         }
6532         return;
6533     } else if (strncmp(message, "opponent mates", 14) == 0) {
6534         switch (gameMode) {
6535           case MachinePlaysBlack:
6536           case IcsPlayingBlack:
6537             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6538             break;
6539           case MachinePlaysWhite:
6540           case IcsPlayingWhite:
6541             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6542             break;
6543           case TwoMachinesPlay:
6544             if (cps->twoMachinesColor[0] == 'w')
6545               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6546             else
6547               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6548             break;
6549           default:
6550             /* can't happen */
6551             break;
6552         }
6553         return;
6554     } else if (strncmp(message, "computer mates", 14) == 0) {
6555         switch (gameMode) {
6556           case MachinePlaysBlack:
6557           case IcsPlayingBlack:
6558             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6559             break;
6560           case MachinePlaysWhite:
6561           case IcsPlayingWhite:
6562             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6563             break;
6564           case TwoMachinesPlay:
6565             if (cps->twoMachinesColor[0] == 'w')
6566               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6567             else
6568               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6569             break;
6570           default:
6571             /* can't happen */
6572             break;
6573         }
6574         return;
6575     } else if (strncmp(message, "checkmate", 9) == 0) {
6576         if (WhiteOnMove(forwardMostMove)) {
6577             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6578         } else {
6579             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6580         }
6581         return;
6582     } else if (strstr(message, "Draw") != NULL ||
6583                strstr(message, "game is a draw") != NULL) {
6584         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6585         return;
6586     } else if (strstr(message, "offer") != NULL &&
6587                strstr(message, "draw") != NULL) {
6588 #if ZIPPY
6589         if (appData.zippyPlay && first.initDone) {
6590             /* Relay offer to ICS */
6591             SendToICS(ics_prefix);
6592             SendToICS("draw\n");
6593         }
6594 #endif
6595         cps->offeredDraw = 2; /* valid until this engine moves twice */
6596         if (gameMode == TwoMachinesPlay) {
6597             if (cps->other->offeredDraw) {
6598                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6599             /* [HGM] in two-machine mode we delay relaying draw offer      */
6600             /* until after we also have move, to see if it is really claim */
6601             }
6602         } else if (gameMode == MachinePlaysWhite ||
6603                    gameMode == MachinePlaysBlack) {
6604           if (userOfferedDraw) {
6605             DisplayInformation(_("Machine accepts your draw offer"));
6606             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6607           } else {
6608             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6609           }
6610         }
6611     }
6612
6613
6614     /*
6615      * Look for thinking output
6616      */
6617     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6618           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6619                                 ) {
6620         int plylev, mvleft, mvtot, curscore, time;
6621         char mvname[MOVE_LEN];
6622         u64 nodes; // [DM]
6623         char plyext;
6624         int ignore = FALSE;
6625         int prefixHint = FALSE;
6626         mvname[0] = NULLCHAR;
6627
6628         switch (gameMode) {
6629           case MachinePlaysBlack:
6630           case IcsPlayingBlack:
6631             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6632             break;
6633           case MachinePlaysWhite:
6634           case IcsPlayingWhite:
6635             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6636             break;
6637           case AnalyzeMode:
6638           case AnalyzeFile:
6639             break;
6640           case IcsObserving: /* [DM] icsEngineAnalyze */
6641             if (!appData.icsEngineAnalyze) ignore = TRUE;
6642             break;
6643           case TwoMachinesPlay:
6644             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6645                 ignore = TRUE;
6646             }
6647             break;
6648           default:
6649             ignore = TRUE;
6650             break;
6651         }
6652
6653         if (!ignore) {
6654             buf1[0] = NULLCHAR;
6655             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6656                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6657
6658                 if (plyext != ' ' && plyext != '\t') {
6659                     time *= 100;
6660                 }
6661
6662                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6663                 if( cps->scoreIsAbsolute &&
6664                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6665                 {
6666                     curscore = -curscore;
6667                 }
6668
6669
6670                 programStats.depth = plylev;
6671                 programStats.nodes = nodes;
6672                 programStats.time = time;
6673                 programStats.score = curscore;
6674                 programStats.got_only_move = 0;
6675
6676                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6677                         int ticklen;
6678
6679                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6680                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6681                         if(WhiteOnMove(forwardMostMove))
6682                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6683                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6684                 }
6685
6686                 /* Buffer overflow protection */
6687                 if (buf1[0] != NULLCHAR) {
6688                     if (strlen(buf1) >= sizeof(programStats.movelist)
6689                         && appData.debugMode) {
6690                         fprintf(debugFP,
6691                                 "PV is too long; using the first %d bytes.\n",
6692                                 sizeof(programStats.movelist) - 1);
6693                     }
6694
6695                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6696                 } else {
6697                     sprintf(programStats.movelist, " no PV\n");
6698                 }
6699
6700                 if (programStats.seen_stat) {
6701                     programStats.ok_to_send = 1;
6702                 }
6703
6704                 if (strchr(programStats.movelist, '(') != NULL) {
6705                     programStats.line_is_book = 1;
6706                     programStats.nr_moves = 0;
6707                     programStats.moves_left = 0;
6708                 } else {
6709                     programStats.line_is_book = 0;
6710                 }
6711
6712                 SendProgramStatsToFrontend( cps, &programStats );
6713
6714                 /*
6715                     [AS] Protect the thinkOutput buffer from overflow... this
6716                     is only useful if buf1 hasn't overflowed first!
6717                 */
6718                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6719                         plylev,
6720                         (gameMode == TwoMachinesPlay ?
6721                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6722                         ((double) curscore) / 100.0,
6723                         prefixHint ? lastHint : "",
6724                         prefixHint ? " " : "" );
6725
6726                 if( buf1[0] != NULLCHAR ) {
6727                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6728
6729                     if( strlen(buf1) > max_len ) {
6730                         if( appData.debugMode) {
6731                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6732                         }
6733                         buf1[max_len+1] = '\0';
6734                     }
6735
6736                     strcat( thinkOutput, buf1 );
6737                 }
6738
6739                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6740                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6741                     DisplayMove(currentMove - 1);
6742                     DisplayAnalysis();
6743                 }
6744                 return;
6745
6746             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6747                 /* crafty (9.25+) says "(only move) <move>"
6748                  * if there is only 1 legal move
6749                  */
6750                 sscanf(p, "(only move) %s", buf1);
6751                 sprintf(thinkOutput, "%s (only move)", buf1);
6752                 sprintf(programStats.movelist, "%s (only move)", buf1);
6753                 programStats.depth = 1;
6754                 programStats.nr_moves = 1;
6755                 programStats.moves_left = 1;
6756                 programStats.nodes = 1;
6757                 programStats.time = 1;
6758                 programStats.got_only_move = 1;
6759
6760                 /* Not really, but we also use this member to
6761                    mean "line isn't going to change" (Crafty
6762                    isn't searching, so stats won't change) */
6763                 programStats.line_is_book = 1;
6764
6765                 SendProgramStatsToFrontend( cps, &programStats );
6766
6767                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6768                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6769                     DisplayMove(currentMove - 1);
6770                     DisplayAnalysis();
6771                 }
6772                 return;
6773             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6774                               &time, &nodes, &plylev, &mvleft,
6775                               &mvtot, mvname) >= 5) {
6776                 /* The stat01: line is from Crafty (9.29+) in response
6777                    to the "." command */
6778                 programStats.seen_stat = 1;
6779                 cps->maybeThinking = TRUE;
6780
6781                 if (programStats.got_only_move || !appData.periodicUpdates)
6782                   return;
6783
6784                 programStats.depth = plylev;
6785                 programStats.time = time;
6786                 programStats.nodes = nodes;
6787                 programStats.moves_left = mvleft;
6788                 programStats.nr_moves = mvtot;
6789                 strcpy(programStats.move_name, mvname);
6790                 programStats.ok_to_send = 1;
6791                 programStats.movelist[0] = '\0';
6792
6793                 SendProgramStatsToFrontend( cps, &programStats );
6794
6795                 DisplayAnalysis();
6796                 return;
6797
6798             } else if (strncmp(message,"++",2) == 0) {
6799                 /* Crafty 9.29+ outputs this */
6800                 programStats.got_fail = 2;
6801                 return;
6802
6803             } else if (strncmp(message,"--",2) == 0) {
6804                 /* Crafty 9.29+ outputs this */
6805                 programStats.got_fail = 1;
6806                 return;
6807
6808             } else if (thinkOutput[0] != NULLCHAR &&
6809                        strncmp(message, "    ", 4) == 0) {
6810                 unsigned message_len;
6811
6812                 p = message;
6813                 while (*p && *p == ' ') p++;
6814
6815                 message_len = strlen( p );
6816
6817                 /* [AS] Avoid buffer overflow */
6818                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6819                     strcat(thinkOutput, " ");
6820                     strcat(thinkOutput, p);
6821                 }
6822
6823                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6824                     strcat(programStats.movelist, " ");
6825                     strcat(programStats.movelist, p);
6826                 }
6827
6828                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6829                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6830                     DisplayMove(currentMove - 1);
6831                     DisplayAnalysis();
6832                 }
6833                 return;
6834             }
6835         }
6836         else {
6837             buf1[0] = NULLCHAR;
6838
6839             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6840                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6841             {
6842                 ChessProgramStats cpstats;
6843
6844                 if (plyext != ' ' && plyext != '\t') {
6845                     time *= 100;
6846                 }
6847
6848                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6849                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6850                     curscore = -curscore;
6851                 }
6852
6853                 cpstats.depth = plylev;
6854                 cpstats.nodes = nodes;
6855                 cpstats.time = time;
6856                 cpstats.score = curscore;
6857                 cpstats.got_only_move = 0;
6858                 cpstats.movelist[0] = '\0';
6859
6860                 if (buf1[0] != NULLCHAR) {
6861                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6862                 }
6863
6864                 cpstats.ok_to_send = 0;
6865                 cpstats.line_is_book = 0;
6866                 cpstats.nr_moves = 0;
6867                 cpstats.moves_left = 0;
6868
6869                 SendProgramStatsToFrontend( cps, &cpstats );
6870             }
6871         }
6872     }
6873 }
6874
6875
6876 /* Parse a game score from the character string "game", and
6877    record it as the history of the current game.  The game
6878    score is NOT assumed to start from the standard position.
6879    The display is not updated in any way.
6880    */
6881 void
6882 ParseGameHistory(game)
6883      char *game;
6884 {
6885     ChessMove moveType;
6886     int fromX, fromY, toX, toY, boardIndex;
6887     char promoChar;
6888     char *p, *q;
6889     char buf[MSG_SIZ];
6890
6891     if (appData.debugMode)
6892       fprintf(debugFP, "Parsing game history: %s\n", game);
6893
6894     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6895     gameInfo.site = StrSave(appData.icsHost);
6896     gameInfo.date = PGNDate();
6897     gameInfo.round = StrSave("-");
6898
6899     /* Parse out names of players */
6900     while (*game == ' ') game++;
6901     p = buf;
6902     while (*game != ' ') *p++ = *game++;
6903     *p = NULLCHAR;
6904     gameInfo.white = StrSave(buf);
6905     while (*game == ' ') game++;
6906     p = buf;
6907     while (*game != ' ' && *game != '\n') *p++ = *game++;
6908     *p = NULLCHAR;
6909     gameInfo.black = StrSave(buf);
6910
6911     /* Parse moves */
6912     boardIndex = blackPlaysFirst ? 1 : 0;
6913     yynewstr(game);
6914     for (;;) {
6915         yyboardindex = boardIndex;
6916         moveType = (ChessMove) yylex();
6917         switch (moveType) {
6918           case IllegalMove:             /* maybe suicide chess, etc. */
6919   if (appData.debugMode) {
6920     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6921     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6922     setbuf(debugFP, NULL);
6923   }
6924           case WhitePromotionChancellor:
6925           case BlackPromotionChancellor:
6926           case WhitePromotionArchbishop:
6927           case BlackPromotionArchbishop:
6928           case WhitePromotionQueen:
6929           case BlackPromotionQueen:
6930           case WhitePromotionRook:
6931           case BlackPromotionRook:
6932           case WhitePromotionBishop:
6933           case BlackPromotionBishop:
6934           case WhitePromotionKnight:
6935           case BlackPromotionKnight:
6936           case WhitePromotionKing:
6937           case BlackPromotionKing:
6938           case NormalMove:
6939           case WhiteCapturesEnPassant:
6940           case BlackCapturesEnPassant:
6941           case WhiteKingSideCastle:
6942           case WhiteQueenSideCastle:
6943           case BlackKingSideCastle:
6944           case BlackQueenSideCastle:
6945           case WhiteKingSideCastleWild:
6946           case WhiteQueenSideCastleWild:
6947           case BlackKingSideCastleWild:
6948           case BlackQueenSideCastleWild:
6949           /* PUSH Fabien */
6950           case WhiteHSideCastleFR:
6951           case WhiteASideCastleFR:
6952           case BlackHSideCastleFR:
6953           case BlackASideCastleFR:
6954           /* POP Fabien */
6955             fromX = currentMoveString[0] - AAA;
6956             fromY = currentMoveString[1] - ONE;
6957             toX = currentMoveString[2] - AAA;
6958             toY = currentMoveString[3] - ONE;
6959             promoChar = currentMoveString[4];
6960             break;
6961           case WhiteDrop:
6962           case BlackDrop:
6963             fromX = moveType == WhiteDrop ?
6964               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6965             (int) CharToPiece(ToLower(currentMoveString[0]));
6966             fromY = DROP_RANK;
6967             toX = currentMoveString[2] - AAA;
6968             toY = currentMoveString[3] - ONE;
6969             promoChar = NULLCHAR;
6970             break;
6971           case AmbiguousMove:
6972             /* bug? */
6973             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6974   if (appData.debugMode) {
6975     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6976     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6977     setbuf(debugFP, NULL);
6978   }
6979             DisplayError(buf, 0);
6980             return;
6981           case ImpossibleMove:
6982             /* bug? */
6983             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6984   if (appData.debugMode) {
6985     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6986     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6987     setbuf(debugFP, NULL);
6988   }
6989             DisplayError(buf, 0);
6990             return;
6991           case (ChessMove) 0:   /* end of file */
6992             if (boardIndex < backwardMostMove) {
6993                 /* Oops, gap.  How did that happen? */
6994                 DisplayError(_("Gap in move list"), 0);
6995                 return;
6996             }
6997             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6998             if (boardIndex > forwardMostMove) {
6999                 forwardMostMove = boardIndex;
7000             }
7001             return;
7002           case ElapsedTime:
7003             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7004                 strcat(parseList[boardIndex-1], " ");
7005                 strcat(parseList[boardIndex-1], yy_text);
7006             }
7007             continue;
7008           case Comment:
7009           case PGNTag:
7010           case NAG:
7011           default:
7012             /* ignore */
7013             continue;
7014           case WhiteWins:
7015           case BlackWins:
7016           case GameIsDrawn:
7017           case GameUnfinished:
7018             if (gameMode == IcsExamining) {
7019                 if (boardIndex < backwardMostMove) {
7020                     /* Oops, gap.  How did that happen? */
7021                     return;
7022                 }
7023                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7024                 return;
7025             }
7026             gameInfo.result = moveType;
7027             p = strchr(yy_text, '{');
7028             if (p == NULL) p = strchr(yy_text, '(');
7029             if (p == NULL) {
7030                 p = yy_text;
7031                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7032             } else {
7033                 q = strchr(p, *p == '{' ? '}' : ')');
7034                 if (q != NULL) *q = NULLCHAR;
7035                 p++;
7036             }
7037             gameInfo.resultDetails = StrSave(p);
7038             continue;
7039         }
7040         if (boardIndex >= forwardMostMove &&
7041             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7042             backwardMostMove = blackPlaysFirst ? 1 : 0;
7043             return;
7044         }
7045         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7046                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7047                                  parseList[boardIndex]);
7048         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7049         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7050         /* currentMoveString is set as a side-effect of yylex */
7051         strcpy(moveList[boardIndex], currentMoveString);
7052         strcat(moveList[boardIndex], "\n");
7053         boardIndex++;
7054         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7055                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7056         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7057                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7058           case MT_NONE:
7059           case MT_STALEMATE:
7060           default:
7061             break;
7062           case MT_CHECK:
7063             if(gameInfo.variant != VariantShogi)
7064                 strcat(parseList[boardIndex - 1], "+");
7065             break;
7066           case MT_CHECKMATE:
7067           case MT_STAINMATE:
7068             strcat(parseList[boardIndex - 1], "#");
7069             break;
7070         }
7071     }
7072 }
7073
7074
7075 /* Apply a move to the given board  */
7076 void
7077 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7078      int fromX, fromY, toX, toY;
7079      int promoChar;
7080      Board board;
7081      char *castling;
7082      char *ep;
7083 {
7084   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7085
7086     /* [HGM] compute & store e.p. status and castling rights for new position */
7087     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7088     { int i;
7089
7090       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7091       oldEP = *ep;
7092       *ep = EP_NONE;
7093
7094       if( board[toY][toX] != EmptySquare )
7095            *ep = EP_CAPTURE;
7096
7097       if( board[fromY][fromX] == WhitePawn ) {
7098            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7099                *ep = EP_PAWN_MOVE;
7100            if( toY-fromY==2) {
7101                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7102                         gameInfo.variant != VariantBerolina || toX < fromX)
7103                       *ep = toX | berolina;
7104                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7105                         gameInfo.variant != VariantBerolina || toX > fromX)
7106                       *ep = toX;
7107            }
7108       } else
7109       if( board[fromY][fromX] == BlackPawn ) {
7110            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7111                *ep = EP_PAWN_MOVE;
7112            if( toY-fromY== -2) {
7113                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7114                         gameInfo.variant != VariantBerolina || toX < fromX)
7115                       *ep = toX | berolina;
7116                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7117                         gameInfo.variant != VariantBerolina || toX > fromX)
7118                       *ep = toX;
7119            }
7120        }
7121
7122        for(i=0; i<nrCastlingRights; i++) {
7123            if(castling[i] == fromX && castlingRank[i] == fromY ||
7124               castling[i] == toX   && castlingRank[i] == toY
7125              ) castling[i] = -1; // revoke for moved or captured piece
7126        }
7127
7128     }
7129
7130   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7131   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7132        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7133
7134   if (fromX == toX && fromY == toY) return;
7135
7136   if (fromY == DROP_RANK) {
7137         /* must be first */
7138         piece = board[toY][toX] = (ChessSquare) fromX;
7139   } else {
7140      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7141      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7142      if(gameInfo.variant == VariantKnightmate)
7143          king += (int) WhiteUnicorn - (int) WhiteKing;
7144
7145     /* Code added by Tord: */
7146     /* FRC castling assumed when king captures friendly rook. */
7147     if (board[fromY][fromX] == WhiteKing &&
7148              board[toY][toX] == WhiteRook) {
7149       board[fromY][fromX] = EmptySquare;
7150       board[toY][toX] = EmptySquare;
7151       if(toX > fromX) {
7152         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7153       } else {
7154         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7155       }
7156     } else if (board[fromY][fromX] == BlackKing &&
7157                board[toY][toX] == BlackRook) {
7158       board[fromY][fromX] = EmptySquare;
7159       board[toY][toX] = EmptySquare;
7160       if(toX > fromX) {
7161         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7162       } else {
7163         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7164       }
7165     /* End of code added by Tord */
7166
7167     } else if (board[fromY][fromX] == king
7168         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7169         && toY == fromY && toX > fromX+1) {
7170         board[fromY][fromX] = EmptySquare;
7171         board[toY][toX] = king;
7172         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7173         board[fromY][BOARD_RGHT-1] = EmptySquare;
7174     } else if (board[fromY][fromX] == king
7175         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7176                && toY == fromY && toX < fromX-1) {
7177         board[fromY][fromX] = EmptySquare;
7178         board[toY][toX] = king;
7179         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7180         board[fromY][BOARD_LEFT] = EmptySquare;
7181     } else if (board[fromY][fromX] == WhitePawn
7182                && toY == BOARD_HEIGHT-1
7183                && gameInfo.variant != VariantXiangqi
7184                ) {
7185         /* white pawn promotion */
7186         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7187         if (board[toY][toX] == EmptySquare) {
7188             board[toY][toX] = WhiteQueen;
7189         }
7190         if(gameInfo.variant==VariantBughouse ||
7191            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7192             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7193         board[fromY][fromX] = EmptySquare;
7194     } else if ((fromY == BOARD_HEIGHT-4)
7195                && (toX != fromX)
7196                && gameInfo.variant != VariantXiangqi
7197                && gameInfo.variant != VariantBerolina
7198                && (board[fromY][fromX] == WhitePawn)
7199                && (board[toY][toX] == EmptySquare)) {
7200         board[fromY][fromX] = EmptySquare;
7201         board[toY][toX] = WhitePawn;
7202         captured = board[toY - 1][toX];
7203         board[toY - 1][toX] = EmptySquare;
7204     } else if ((fromY == BOARD_HEIGHT-4)
7205                && (toX == fromX)
7206                && gameInfo.variant == VariantBerolina
7207                && (board[fromY][fromX] == WhitePawn)
7208                && (board[toY][toX] == EmptySquare)) {
7209         board[fromY][fromX] = EmptySquare;
7210         board[toY][toX] = WhitePawn;
7211         if(oldEP & EP_BEROLIN_A) {
7212                 captured = board[fromY][fromX-1];
7213                 board[fromY][fromX-1] = EmptySquare;
7214         }else{  captured = board[fromY][fromX+1];
7215                 board[fromY][fromX+1] = EmptySquare;
7216         }
7217     } else if (board[fromY][fromX] == king
7218         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7219                && toY == fromY && toX > fromX+1) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = king;
7222         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7223         board[fromY][BOARD_RGHT-1] = EmptySquare;
7224     } else if (board[fromY][fromX] == king
7225         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7226                && toY == fromY && toX < fromX-1) {
7227         board[fromY][fromX] = EmptySquare;
7228         board[toY][toX] = king;
7229         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7230         board[fromY][BOARD_LEFT] = EmptySquare;
7231     } else if (fromY == 7 && fromX == 3
7232                && board[fromY][fromX] == BlackKing
7233                && toY == 7 && toX == 5) {
7234         board[fromY][fromX] = EmptySquare;
7235         board[toY][toX] = BlackKing;
7236         board[fromY][7] = EmptySquare;
7237         board[toY][4] = BlackRook;
7238     } else if (fromY == 7 && fromX == 3
7239                && board[fromY][fromX] == BlackKing
7240                && toY == 7 && toX == 1) {
7241         board[fromY][fromX] = EmptySquare;
7242         board[toY][toX] = BlackKing;
7243         board[fromY][0] = EmptySquare;
7244         board[toY][2] = BlackRook;
7245     } else if (board[fromY][fromX] == BlackPawn
7246                && toY == 0
7247                && gameInfo.variant != VariantXiangqi
7248                ) {
7249         /* black pawn promotion */
7250         board[0][toX] = CharToPiece(ToLower(promoChar));
7251         if (board[0][toX] == EmptySquare) {
7252             board[0][toX] = BlackQueen;
7253         }
7254         if(gameInfo.variant==VariantBughouse ||
7255            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7256             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7257         board[fromY][fromX] = EmptySquare;
7258     } else if ((fromY == 3)
7259                && (toX != fromX)
7260                && gameInfo.variant != VariantXiangqi
7261                && gameInfo.variant != VariantBerolina
7262                && (board[fromY][fromX] == BlackPawn)
7263                && (board[toY][toX] == EmptySquare)) {
7264         board[fromY][fromX] = EmptySquare;
7265         board[toY][toX] = BlackPawn;
7266         captured = board[toY + 1][toX];
7267         board[toY + 1][toX] = EmptySquare;
7268     } else if ((fromY == 3)
7269                && (toX == fromX)
7270                && gameInfo.variant == VariantBerolina
7271                && (board[fromY][fromX] == BlackPawn)
7272                && (board[toY][toX] == EmptySquare)) {
7273         board[fromY][fromX] = EmptySquare;
7274         board[toY][toX] = BlackPawn;
7275         if(oldEP & EP_BEROLIN_A) {
7276                 captured = board[fromY][fromX-1];
7277                 board[fromY][fromX-1] = EmptySquare;
7278         }else{  captured = board[fromY][fromX+1];
7279                 board[fromY][fromX+1] = EmptySquare;
7280         }
7281     } else {
7282         board[toY][toX] = board[fromY][fromX];
7283         board[fromY][fromX] = EmptySquare;
7284     }
7285
7286     /* [HGM] now we promote for Shogi, if needed */
7287     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7288         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7289   }
7290
7291     if (gameInfo.holdingsWidth != 0) {
7292
7293       /* !!A lot more code needs to be written to support holdings  */
7294       /* [HGM] OK, so I have written it. Holdings are stored in the */
7295       /* penultimate board files, so they are automaticlly stored   */
7296       /* in the game history.                                       */
7297       if (fromY == DROP_RANK) {
7298         /* Delete from holdings, by decreasing count */
7299         /* and erasing image if necessary            */
7300         p = (int) fromX;
7301         if(p < (int) BlackPawn) { /* white drop */
7302              p -= (int)WhitePawn;
7303              if(p >= gameInfo.holdingsSize) p = 0;
7304              if(--board[p][BOARD_WIDTH-2] == 0)
7305                   board[p][BOARD_WIDTH-1] = EmptySquare;
7306         } else {                  /* black drop */
7307              p -= (int)BlackPawn;
7308              if(p >= gameInfo.holdingsSize) p = 0;
7309              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7310                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7311         }
7312       }
7313       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7314           && gameInfo.variant != VariantBughouse        ) {
7315         /* [HGM] holdings: Add to holdings, if holdings exist */
7316         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7317                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7318                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7319         }
7320         p = (int) captured;
7321         if (p >= (int) BlackPawn) {
7322           p -= (int)BlackPawn;
7323           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7324                   /* in Shogi restore piece to its original  first */
7325                   captured = (ChessSquare) (DEMOTED captured);
7326                   p = DEMOTED p;
7327           }
7328           p = PieceToNumber((ChessSquare)p);
7329           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7330           board[p][BOARD_WIDTH-2]++;
7331           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7332         } else {
7333           p -= (int)WhitePawn;
7334           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7335                   captured = (ChessSquare) (DEMOTED captured);
7336                   p = DEMOTED p;
7337           }
7338           p = PieceToNumber((ChessSquare)p);
7339           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7340           board[BOARD_HEIGHT-1-p][1]++;
7341           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7342         }
7343       }
7344
7345     } else if (gameInfo.variant == VariantAtomic) {
7346       if (captured != EmptySquare) {
7347         int y, x;
7348         for (y = toY-1; y <= toY+1; y++) {
7349           for (x = toX-1; x <= toX+1; x++) {
7350             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7351                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7352               board[y][x] = EmptySquare;
7353             }
7354           }
7355         }
7356         board[toY][toX] = EmptySquare;
7357       }
7358     }
7359     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7360         /* [HGM] Shogi promotions */
7361         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7362     }
7363
7364     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7365                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7366         // [HGM] superchess: take promotion piece out of holdings
7367         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7368         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7369             if(!--board[k][BOARD_WIDTH-2])
7370                 board[k][BOARD_WIDTH-1] = EmptySquare;
7371         } else {
7372             if(!--board[BOARD_HEIGHT-1-k][1])
7373                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7374         }
7375     }
7376
7377 }
7378
7379 /* Updates forwardMostMove */
7380 void
7381 MakeMove(fromX, fromY, toX, toY, promoChar)
7382      int fromX, fromY, toX, toY;
7383      int promoChar;
7384 {
7385 //    forwardMostMove++; // [HGM] bare: moved downstream
7386
7387     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7388         int timeLeft; static int lastLoadFlag=0; int king, piece;
7389         piece = boards[forwardMostMove][fromY][fromX];
7390         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7391         if(gameInfo.variant == VariantKnightmate)
7392             king += (int) WhiteUnicorn - (int) WhiteKing;
7393         if(forwardMostMove == 0) {
7394             if(blackPlaysFirst)
7395                 fprintf(serverMoves, "%s;", second.tidy);
7396             fprintf(serverMoves, "%s;", first.tidy);
7397             if(!blackPlaysFirst)
7398                 fprintf(serverMoves, "%s;", second.tidy);
7399         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7400         lastLoadFlag = loadFlag;
7401         // print base move
7402         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7403         // print castling suffix
7404         if( toY == fromY && piece == king ) {
7405             if(toX-fromX > 1)
7406                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7407             if(fromX-toX >1)
7408                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7409         }
7410         // e.p. suffix
7411         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7412              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7413              boards[forwardMostMove][toY][toX] == EmptySquare
7414              && fromX != toX )
7415                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7416         // promotion suffix
7417         if(promoChar != NULLCHAR)
7418                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7419         if(!loadFlag) {
7420             fprintf(serverMoves, "/%d/%d",
7421                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7422             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7423             else                      timeLeft = blackTimeRemaining/1000;
7424             fprintf(serverMoves, "/%d", timeLeft);
7425         }
7426         fflush(serverMoves);
7427     }
7428
7429     if (forwardMostMove+1 >= MAX_MOVES) {
7430       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7431                         0, 1);
7432       return;
7433     }
7434     if (commentList[forwardMostMove+1] != NULL) {
7435         free(commentList[forwardMostMove+1]);
7436         commentList[forwardMostMove+1] = NULL;
7437     }
7438     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7439     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7440     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7441                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7442     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7443     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7444     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7445     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7446     gameInfo.result = GameUnfinished;
7447     if (gameInfo.resultDetails != NULL) {
7448         free(gameInfo.resultDetails);
7449         gameInfo.resultDetails = NULL;
7450     }
7451     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7452                               moveList[forwardMostMove - 1]);
7453     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7454                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7455                              fromY, fromX, toY, toX, promoChar,
7456                              parseList[forwardMostMove - 1]);
7457     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7458                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7459                             castlingRights[forwardMostMove]) ) {
7460       case MT_NONE:
7461       case MT_STALEMATE:
7462       default:
7463         break;
7464       case MT_CHECK:
7465         if(gameInfo.variant != VariantShogi)
7466             strcat(parseList[forwardMostMove - 1], "+");
7467         break;
7468       case MT_CHECKMATE:
7469       case MT_STAINMATE:
7470         strcat(parseList[forwardMostMove - 1], "#");
7471         break;
7472     }
7473     if (appData.debugMode) {
7474         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7475     }
7476
7477 }
7478
7479 /* Updates currentMove if not pausing */
7480 void
7481 ShowMove(fromX, fromY, toX, toY)
7482 {
7483     int instant = (gameMode == PlayFromGameFile) ?
7484         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7485
7486     if(appData.noGUI) return;
7487
7488     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7489       {
7490         if (!instant)
7491           {
7492             if (forwardMostMove == currentMove + 1)
7493               {
7494 //TODO
7495 //              AnimateMove(boards[forwardMostMove - 1],
7496 //                          fromX, fromY, toX, toY);
7497               }
7498             if (appData.highlightLastMove)
7499               {
7500                 SetHighlights(fromX, fromY, toX, toY);
7501               }
7502           }
7503         currentMove = forwardMostMove;
7504     }
7505
7506     if (instant) return;
7507
7508     DisplayMove(currentMove - 1);
7509     DrawPosition(FALSE, boards[currentMove]);
7510     DisplayBothClocks();
7511     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7512
7513     return;
7514 }
7515
7516 void SendEgtPath(ChessProgramState *cps)
7517 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7518         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7519
7520         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7521
7522         while(*p) {
7523             char c, *q = name+1, *r, *s;
7524
7525             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7526             while(*p && *p != ',') *q++ = *p++;
7527             *q++ = ':'; *q = 0;
7528             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7529                 strcmp(name, ",nalimov:") == 0 ) {
7530                 // take nalimov path from the menu-changeable option first, if it is defined
7531                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7532                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7533             } else
7534             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7535                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7536                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7537                 s = r = StrStr(s, ":") + 1; // beginning of path info
7538                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7539                 c = *r; *r = 0;             // temporarily null-terminate path info
7540                     *--q = 0;               // strip of trailig ':' from name
7541                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7542                 *r = c;
7543                 SendToProgram(buf,cps);     // send egtbpath command for this format
7544             }
7545             if(*p == ',') p++; // read away comma to position for next format name
7546         }
7547 }
7548
7549 void
7550 InitChessProgram(cps, setup)
7551      ChessProgramState *cps;
7552      int setup; /* [HGM] needed to setup FRC opening position */
7553 {
7554     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7555     if (appData.noChessProgram) return;
7556     hintRequested = FALSE;
7557     bookRequested = FALSE;
7558
7559     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7560     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7561     if(cps->memSize) { /* [HGM] memory */
7562         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7563         SendToProgram(buf, cps);
7564     }
7565     SendEgtPath(cps); /* [HGM] EGT */
7566     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7567         sprintf(buf, "cores %d\n", appData.smpCores);
7568         SendToProgram(buf, cps);
7569     }
7570
7571     SendToProgram(cps->initString, cps);
7572     if (gameInfo.variant != VariantNormal &&
7573         gameInfo.variant != VariantLoadable
7574         /* [HGM] also send variant if board size non-standard */
7575         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7576                                             ) {
7577       char *v = VariantName(gameInfo.variant);
7578       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7579         /* [HGM] in protocol 1 we have to assume all variants valid */
7580         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7581         DisplayFatalError(buf, 0, 1);
7582         return;
7583       }
7584
7585       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7586       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7587       if( gameInfo.variant == VariantXiangqi )
7588            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7589       if( gameInfo.variant == VariantShogi )
7590            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7591       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7592            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7593       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7594                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7595            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7596       if( gameInfo.variant == VariantCourier )
7597            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7598       if( gameInfo.variant == VariantSuper )
7599            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7600       if( gameInfo.variant == VariantGreat )
7601            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7602
7603       if(overruled) {
7604            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7605                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7606            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7607            if(StrStr(cps->variants, b) == NULL) {
7608                // specific sized variant not known, check if general sizing allowed
7609                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7610                    if(StrStr(cps->variants, "boardsize") == NULL) {
7611                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7612                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7613                        DisplayFatalError(buf, 0, 1);
7614                        return;
7615                    }
7616                    /* [HGM] here we really should compare with the maximum supported board size */
7617                }
7618            }
7619       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7620       sprintf(buf, "variant %s\n", b);
7621       SendToProgram(buf, cps);
7622     }
7623     currentlyInitializedVariant = gameInfo.variant;
7624
7625     /* [HGM] send opening position in FRC to first engine */
7626     if(setup) {
7627           SendToProgram("force\n", cps);
7628           SendBoard(cps, 0);
7629           /* engine is now in force mode! Set flag to wake it up after first move. */
7630           setboardSpoiledMachineBlack = 1;
7631     }
7632
7633     if (cps->sendICS) {
7634       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7635       SendToProgram(buf, cps);
7636     }
7637     cps->maybeThinking = FALSE;
7638     cps->offeredDraw = 0;
7639     if (!appData.icsActive) {
7640         SendTimeControl(cps, movesPerSession, timeControl,
7641                         timeIncrement, appData.searchDepth,
7642                         searchTime);
7643     }
7644     if (appData.showThinking
7645         // [HGM] thinking: four options require thinking output to be sent
7646         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7647                                 ) {
7648         SendToProgram("post\n", cps);
7649     }
7650     SendToProgram("hard\n", cps);
7651     if (!appData.ponderNextMove) {
7652         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7653            it without being sure what state we are in first.  "hard"
7654            is not a toggle, so that one is OK.
7655          */
7656         SendToProgram("easy\n", cps);
7657     }
7658     if (cps->usePing) {
7659       sprintf(buf, "ping %d\n", ++cps->lastPing);
7660       SendToProgram(buf, cps);
7661     }
7662     cps->initDone = TRUE;
7663 }
7664
7665
7666 void
7667 StartChessProgram(cps)
7668      ChessProgramState *cps;
7669 {
7670     char buf[MSG_SIZ];
7671     int err;
7672
7673     if (appData.noChessProgram) return;
7674     cps->initDone = FALSE;
7675
7676     if (strcmp(cps->host, "localhost") == 0) {
7677         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7678     } else if (*appData.remoteShell == NULLCHAR) {
7679         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7680     } else {
7681         if (*appData.remoteUser == NULLCHAR) {
7682           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7683                     cps->program);
7684         } else {
7685           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7686                     cps->host, appData.remoteUser, cps->program);
7687         }
7688         err = StartChildProcess(buf, "", &cps->pr);
7689     }
7690
7691     if (err != 0) {
7692         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7693         DisplayFatalError(buf, err, 1);
7694         cps->pr = NoProc;
7695         cps->isr = NULL;
7696         return;
7697     }
7698
7699     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7700     if (cps->protocolVersion > 1) {
7701       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7702       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7703       cps->comboCnt = 0;  //                and values of combo boxes
7704       SendToProgram(buf, cps);
7705     } else {
7706       SendToProgram("xboard\n", cps);
7707     }
7708 }
7709
7710
7711 void
7712 TwoMachinesEventIfReady P((void))
7713 {
7714   if (first.lastPing != first.lastPong) {
7715     DisplayMessage("", _("Waiting for first chess program"));
7716     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7717     return;
7718   }
7719   if (second.lastPing != second.lastPong) {
7720     DisplayMessage("", _("Waiting for second chess program"));
7721     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7722     return;
7723   }
7724   ThawUI();
7725   TwoMachinesEvent();
7726 }
7727
7728 void
7729 NextMatchGame P((void))
7730 {
7731     int index; /* [HGM] autoinc: step lod index during match */
7732     Reset(FALSE, TRUE);
7733     if (*appData.loadGameFile != NULLCHAR) {
7734         index = appData.loadGameIndex;
7735         if(index < 0) { // [HGM] autoinc
7736             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7737             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7738         }
7739         LoadGameFromFile(appData.loadGameFile,
7740                          index,
7741                          appData.loadGameFile, FALSE);
7742     } else if (*appData.loadPositionFile != NULLCHAR) {
7743         index = appData.loadPositionIndex;
7744         if(index < 0) { // [HGM] autoinc
7745             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7746             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7747         }
7748         LoadPositionFromFile(appData.loadPositionFile,
7749                              index,
7750                              appData.loadPositionFile);
7751     }
7752     TwoMachinesEventIfReady();
7753 }
7754
7755 void UserAdjudicationEvent( int result )
7756 {
7757     ChessMove gameResult = GameIsDrawn;
7758
7759     if( result > 0 ) {
7760         gameResult = WhiteWins;
7761     }
7762     else if( result < 0 ) {
7763         gameResult = BlackWins;
7764     }
7765
7766     if( gameMode == TwoMachinesPlay ) {
7767         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7768     }
7769 }
7770
7771
7772 // [HGM] save: calculate checksum of game to make games easily identifiable
7773 int StringCheckSum(char *s)
7774 {
7775         int i = 0;
7776         if(s==NULL) return 0;
7777         while(*s) i = i*259 + *s++;
7778         return i;
7779 }
7780
7781 int GameCheckSum()
7782 {
7783         int i, sum=0;
7784         for(i=backwardMostMove; i<forwardMostMove; i++) {
7785                 sum += pvInfoList[i].depth;
7786                 sum += StringCheckSum(parseList[i]);
7787                 sum += StringCheckSum(commentList[i]);
7788                 sum *= 261;
7789         }
7790         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7791         return sum + StringCheckSum(commentList[i]);
7792 } // end of save patch
7793
7794 void
7795 GameEnds(result, resultDetails, whosays)
7796      ChessMove result;
7797      char *resultDetails;
7798      int whosays;
7799 {
7800     GameMode nextGameMode;
7801     int isIcsGame;
7802     char buf[MSG_SIZ];
7803
7804     if(endingGame) return; /* [HGM] crash: forbid recursion */
7805     endingGame = 1;
7806
7807     if (appData.debugMode) {
7808       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7809               result, resultDetails ? resultDetails : "(null)", whosays);
7810     }
7811
7812     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7813         /* If we are playing on ICS, the server decides when the
7814            game is over, but the engine can offer to draw, claim
7815            a draw, or resign.
7816          */
7817 #if ZIPPY
7818         if (appData.zippyPlay && first.initDone) {
7819             if (result == GameIsDrawn) {
7820                 /* In case draw still needs to be claimed */
7821                 SendToICS(ics_prefix);
7822                 SendToICS("draw\n");
7823             } else if (StrCaseStr(resultDetails, "resign")) {
7824                 SendToICS(ics_prefix);
7825                 SendToICS("resign\n");
7826             }
7827         }
7828 #endif
7829         endingGame = 0; /* [HGM] crash */
7830         return;
7831     }
7832
7833     /* If we're loading the game from a file, stop */
7834     if (whosays == GE_FILE) {
7835       (void) StopLoadGameTimer();
7836       gameFileFP = NULL;
7837     }
7838
7839     /* Cancel draw offers */
7840     first.offeredDraw = second.offeredDraw = 0;
7841
7842     /* If this is an ICS game, only ICS can really say it's done;
7843        if not, anyone can. */
7844     isIcsGame = (gameMode == IcsPlayingWhite ||
7845                  gameMode == IcsPlayingBlack ||
7846                  gameMode == IcsObserving    ||
7847                  gameMode == IcsExamining);
7848
7849     if (!isIcsGame || whosays == GE_ICS) {
7850         /* OK -- not an ICS game, or ICS said it was done */
7851         StopClocks();
7852         if (!isIcsGame && !appData.noChessProgram)
7853           SetUserThinkingEnables();
7854
7855         /* [HGM] if a machine claims the game end we verify this claim */
7856         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7857             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7858                 char claimer;
7859                 ChessMove trueResult = (ChessMove) -1;
7860
7861                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7862                                             first.twoMachinesColor[0] :
7863                                             second.twoMachinesColor[0] ;
7864
7865                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7866                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7867                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7868                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7869                 } else
7870                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7871                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7872                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7873                 } else
7874                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7875                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7876                 }
7877
7878                 // now verify win claims, but not in drop games, as we don't understand those yet
7879                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7880                                                  || gameInfo.variant == VariantGreat) &&
7881                     (result == WhiteWins && claimer == 'w' ||
7882                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7883                       if (appData.debugMode) {
7884                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7885                                 result, epStatus[forwardMostMove], forwardMostMove);
7886                       }
7887                       if(result != trueResult) {
7888                               sprintf(buf, "False win claim: '%s'", resultDetails);
7889                               result = claimer == 'w' ? BlackWins : WhiteWins;
7890                               resultDetails = buf;
7891                       }
7892                 } else
7893                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7894                     && (forwardMostMove <= backwardMostMove ||
7895                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7896                         (claimer=='b')==(forwardMostMove&1))
7897                                                                                   ) {
7898                       /* [HGM] verify: draws that were not flagged are false claims */
7899                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7900                       result = claimer == 'w' ? BlackWins : WhiteWins;
7901                       resultDetails = buf;
7902                 }
7903                 /* (Claiming a loss is accepted no questions asked!) */
7904             }
7905
7906             /* [HGM] bare: don't allow bare King to win */
7907             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7908                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7909                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7910                && result != GameIsDrawn)
7911             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7912                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7913                         int p = (int)boards[forwardMostMove][i][j] - color;
7914                         if(p >= 0 && p <= (int)WhiteKing) k++;
7915                 }
7916                 if (appData.debugMode) {
7917                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7918                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7919                 }
7920                 if(k <= 1) {
7921                         result = GameIsDrawn;
7922                         sprintf(buf, "%s but bare king", resultDetails);
7923                         resultDetails = buf;
7924                 }
7925             }
7926         }
7927
7928         if(serverMoves != NULL && !loadFlag) { char c = '=';
7929             if(result==WhiteWins) c = '+';
7930             if(result==BlackWins) c = '-';
7931             if(resultDetails != NULL)
7932                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7933         }
7934         if (resultDetails != NULL) {
7935             gameInfo.result = result;
7936             gameInfo.resultDetails = StrSave(resultDetails);
7937
7938             /* display last move only if game was not loaded from file */
7939             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7940                 DisplayMove(currentMove - 1);
7941
7942             if (forwardMostMove != 0) {
7943                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7944                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7945                                                                 ) {
7946                     if (*appData.saveGameFile != NULLCHAR) {
7947                         SaveGameToFile(appData.saveGameFile, TRUE);
7948                     } else if (appData.autoSaveGames) {
7949                         AutoSaveGame();
7950                     }
7951                     if (*appData.savePositionFile != NULLCHAR) {
7952                         SavePositionToFile(appData.savePositionFile);
7953                     }
7954                 }
7955             }
7956
7957             /* Tell program how game ended in case it is learning */
7958             /* [HGM] Moved this to after saving the PGN, just in case */
7959             /* engine died and we got here through time loss. In that */
7960             /* case we will get a fatal error writing the pipe, which */
7961             /* would otherwise lose us the PGN.                       */
7962             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7963             /* output during GameEnds should never be fatal anymore   */
7964             if (gameMode == MachinePlaysWhite ||
7965                 gameMode == MachinePlaysBlack ||
7966                 gameMode == TwoMachinesPlay ||
7967                 gameMode == IcsPlayingWhite ||
7968                 gameMode == IcsPlayingBlack ||
7969                 gameMode == BeginningOfGame) {
7970                 char buf[MSG_SIZ];
7971                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7972                         resultDetails);
7973                 if (first.pr != NoProc) {
7974                     SendToProgram(buf, &first);
7975                 }
7976                 if (second.pr != NoProc &&
7977                     gameMode == TwoMachinesPlay) {
7978                     SendToProgram(buf, &second);
7979                 }
7980             }
7981         }
7982
7983         if (appData.icsActive) {
7984             if (appData.quietPlay &&
7985                 (gameMode == IcsPlayingWhite ||
7986                  gameMode == IcsPlayingBlack)) {
7987                 SendToICS(ics_prefix);
7988                 SendToICS("set shout 1\n");
7989             }
7990             nextGameMode = IcsIdle;
7991             ics_user_moved = FALSE;
7992             /* clean up premove.  It's ugly when the game has ended and the
7993              * premove highlights are still on the board.
7994              */
7995             if (gotPremove) {
7996               gotPremove = FALSE;
7997               ClearPremoveHighlights();
7998               DrawPosition(FALSE, boards[currentMove]);
7999             }
8000             if (whosays == GE_ICS) {
8001                 switch (result) {
8002                 case WhiteWins:
8003                     if (gameMode == IcsPlayingWhite)
8004                         PlayIcsWinSound();
8005                     else if(gameMode == IcsPlayingBlack)
8006                         PlayIcsLossSound();
8007                     break;
8008                 case BlackWins:
8009                     if (gameMode == IcsPlayingBlack)
8010                         PlayIcsWinSound();
8011                     else if(gameMode == IcsPlayingWhite)
8012                         PlayIcsLossSound();
8013                     break;
8014                 case GameIsDrawn:
8015                     PlayIcsDrawSound();
8016                     break;
8017                 default:
8018                     PlayIcsUnfinishedSound();
8019                 }
8020             }
8021         } else if (gameMode == EditGame ||
8022                    gameMode == PlayFromGameFile ||
8023                    gameMode == AnalyzeMode ||
8024                    gameMode == AnalyzeFile) {
8025             nextGameMode = gameMode;
8026         } else {
8027             nextGameMode = EndOfGame;
8028         }
8029         pausing = FALSE;
8030         ModeHighlight();
8031     } else {
8032         nextGameMode = gameMode;
8033     }
8034
8035     if (appData.noChessProgram) {
8036         gameMode = nextGameMode;
8037         ModeHighlight();
8038         endingGame = 0; /* [HGM] crash */
8039         return;
8040     }
8041
8042     if (first.reuse) {
8043         /* Put first chess program into idle state */
8044         if (first.pr != NoProc &&
8045             (gameMode == MachinePlaysWhite ||
8046              gameMode == MachinePlaysBlack ||
8047              gameMode == TwoMachinesPlay ||
8048              gameMode == IcsPlayingWhite ||
8049              gameMode == IcsPlayingBlack ||
8050              gameMode == BeginningOfGame)) {
8051             SendToProgram("force\n", &first);
8052             if (first.usePing) {
8053               char buf[MSG_SIZ];
8054               sprintf(buf, "ping %d\n", ++first.lastPing);
8055               SendToProgram(buf, &first);
8056             }
8057         }
8058     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8059         /* Kill off first chess program */
8060         if (first.isr != NULL)
8061           RemoveInputSource(first.isr);
8062         first.isr = NULL;
8063
8064         if (first.pr != NoProc) {
8065             ExitAnalyzeMode();
8066             DoSleep( appData.delayBeforeQuit );
8067             SendToProgram("quit\n", &first);
8068             DoSleep( appData.delayAfterQuit );
8069             DestroyChildProcess(first.pr, first.useSigterm);
8070         }
8071         first.pr = NoProc;
8072     }
8073     if (second.reuse) {
8074         /* Put second chess program into idle state */
8075         if (second.pr != NoProc &&
8076             gameMode == TwoMachinesPlay) {
8077             SendToProgram("force\n", &second);
8078             if (second.usePing) {
8079               char buf[MSG_SIZ];
8080               sprintf(buf, "ping %d\n", ++second.lastPing);
8081               SendToProgram(buf, &second);
8082             }
8083         }
8084     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8085         /* Kill off second chess program */
8086         if (second.isr != NULL)
8087           RemoveInputSource(second.isr);
8088         second.isr = NULL;
8089
8090         if (second.pr != NoProc) {
8091             DoSleep( appData.delayBeforeQuit );
8092             SendToProgram("quit\n", &second);
8093             DoSleep( appData.delayAfterQuit );
8094             DestroyChildProcess(second.pr, second.useSigterm);
8095         }
8096         second.pr = NoProc;
8097     }
8098
8099     if (matchMode && gameMode == TwoMachinesPlay) {
8100         switch (result) {
8101         case WhiteWins:
8102           if (first.twoMachinesColor[0] == 'w') {
8103             first.matchWins++;
8104           } else {
8105             second.matchWins++;
8106           }
8107           break;
8108         case BlackWins:
8109           if (first.twoMachinesColor[0] == 'b') {
8110             first.matchWins++;
8111           } else {
8112             second.matchWins++;
8113           }
8114           break;
8115         default:
8116           break;
8117         }
8118         if (matchGame < appData.matchGames) {
8119             char *tmp;
8120             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8121                 tmp = first.twoMachinesColor;
8122                 first.twoMachinesColor = second.twoMachinesColor;
8123                 second.twoMachinesColor = tmp;
8124             }
8125             gameMode = nextGameMode;
8126             matchGame++;
8127             if(appData.matchPause>10000 || appData.matchPause<10)
8128                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8129             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8130             endingGame = 0; /* [HGM] crash */
8131             return;
8132         } else {
8133             char buf[MSG_SIZ];
8134             gameMode = nextGameMode;
8135             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8136                     first.tidy, second.tidy,
8137                     first.matchWins, second.matchWins,
8138                     appData.matchGames - (first.matchWins + second.matchWins));
8139             DisplayFatalError(buf, 0, 0);
8140         }
8141     }
8142     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8143         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8144       ExitAnalyzeMode();
8145     gameMode = nextGameMode;
8146     ModeHighlight();
8147     endingGame = 0;  /* [HGM] crash */
8148 }
8149
8150 /* Assumes program was just initialized (initString sent).
8151    Leaves program in force mode. */
8152 void
8153 FeedMovesToProgram(cps, upto)
8154      ChessProgramState *cps;
8155      int upto;
8156 {
8157     int i;
8158
8159     if (appData.debugMode)
8160       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8161               startedFromSetupPosition ? "position and " : "",
8162               backwardMostMove, upto, cps->which);
8163     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8164         // [HGM] variantswitch: make engine aware of new variant
8165         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8166                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8167         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8168         SendToProgram(buf, cps);
8169         currentlyInitializedVariant = gameInfo.variant;
8170     }
8171     SendToProgram("force\n", cps);
8172     if (startedFromSetupPosition) {
8173         SendBoard(cps, backwardMostMove);
8174     if (appData.debugMode) {
8175         fprintf(debugFP, "feedMoves\n");
8176     }
8177     }
8178     for (i = backwardMostMove; i < upto; i++) {
8179         SendMoveToProgram(i, cps);
8180     }
8181 }
8182
8183
8184 void
8185 ResurrectChessProgram()
8186 {
8187      /* The chess program may have exited.
8188         If so, restart it and feed it all the moves made so far. */
8189
8190     if (appData.noChessProgram || first.pr != NoProc) return;
8191
8192     StartChessProgram(&first);
8193     InitChessProgram(&first, FALSE);
8194     FeedMovesToProgram(&first, currentMove);
8195
8196     if (!first.sendTime) {
8197         /* can't tell gnuchess what its clock should read,
8198            so we bow to its notion. */
8199         ResetClocks();
8200         timeRemaining[0][currentMove] = whiteTimeRemaining;
8201         timeRemaining[1][currentMove] = blackTimeRemaining;
8202     }
8203
8204     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8205                 appData.icsEngineAnalyze) && first.analysisSupport) {
8206       SendToProgram("analyze\n", &first);
8207       first.analyzing = TRUE;
8208     }
8209 }
8210
8211 /*
8212  * Button procedures
8213  */
8214 void
8215 Reset(redraw, init)
8216      int redraw, init;
8217 {
8218     int i;
8219
8220     if (appData.debugMode) {
8221         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8222                 redraw, init, gameMode);
8223     }
8224     pausing = pauseExamInvalid = FALSE;
8225     startedFromSetupPosition = blackPlaysFirst = FALSE;
8226     firstMove = TRUE;
8227     whiteFlag = blackFlag = FALSE;
8228     userOfferedDraw = FALSE;
8229     hintRequested = bookRequested = FALSE;
8230     first.maybeThinking = FALSE;
8231     second.maybeThinking = FALSE;
8232     first.bookSuspend = FALSE; // [HGM] book
8233     second.bookSuspend = FALSE;
8234     thinkOutput[0] = NULLCHAR;
8235     lastHint[0] = NULLCHAR;
8236     ClearGameInfo(&gameInfo);
8237     gameInfo.variant = StringToVariant(appData.variant);
8238     ics_user_moved = ics_clock_paused = FALSE;
8239     ics_getting_history = H_FALSE;
8240     ics_gamenum = -1;
8241     white_holding[0] = black_holding[0] = NULLCHAR;
8242     ClearProgramStats();
8243     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8244
8245     ResetFrontEnd();
8246     ClearHighlights();
8247     flipView = appData.flipView;
8248     ClearPremoveHighlights();
8249     gotPremove = FALSE;
8250     alarmSounded = FALSE;
8251
8252     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8253     if(appData.serverMovesName != NULL) {
8254         /* [HGM] prepare to make moves file for broadcasting */
8255         clock_t t = clock();
8256         if(serverMoves != NULL) fclose(serverMoves);
8257         serverMoves = fopen(appData.serverMovesName, "r");
8258         if(serverMoves != NULL) {
8259             fclose(serverMoves);
8260             /* delay 15 sec before overwriting, so all clients can see end */
8261             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8262         }
8263         serverMoves = fopen(appData.serverMovesName, "w");
8264     }
8265
8266     ExitAnalyzeMode();
8267     gameMode = BeginningOfGame;
8268     ModeHighlight();
8269
8270     if(appData.icsActive) gameInfo.variant = VariantNormal;
8271     currentMove = forwardMostMove = backwardMostMove = 0;
8272     InitPosition(redraw);
8273     for (i = 0; i < MAX_MOVES; i++) {
8274         if (commentList[i] != NULL) {
8275             free(commentList[i]);
8276             commentList[i] = NULL;
8277         }
8278     }
8279
8280     ResetClocks();
8281     timeRemaining[0][0] = whiteTimeRemaining;
8282     timeRemaining[1][0] = blackTimeRemaining;
8283     if (first.pr == NULL) {
8284         StartChessProgram(&first);
8285     }
8286     if (init) {
8287             InitChessProgram(&first, startedFromSetupPosition);
8288     }
8289
8290     DisplayTitle("");
8291     DisplayMessage("", "");
8292     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8293     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8294     return;
8295 }
8296
8297 void
8298 AutoPlayGameLoop()
8299 {
8300     for (;;) {
8301         if (!AutoPlayOneMove())
8302           return;
8303         if (matchMode || appData.timeDelay == 0)
8304           continue;
8305         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8306           return;
8307         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8308         break;
8309     }
8310 }
8311
8312
8313 int
8314 AutoPlayOneMove()
8315 {
8316     int fromX, fromY, toX, toY;
8317
8318     if (appData.debugMode) {
8319       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8320     }
8321
8322     if (gameMode != PlayFromGameFile)
8323       return FALSE;
8324
8325     if (currentMove >= forwardMostMove) {
8326       gameMode = EditGame;
8327       ModeHighlight();
8328
8329       /* [AS] Clear current move marker at the end of a game */
8330       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8331
8332       return FALSE;
8333     }
8334
8335     toX = moveList[currentMove][2] - AAA;
8336     toY = moveList[currentMove][3] - ONE;
8337
8338     if (moveList[currentMove][1] == '@') {
8339         if (appData.highlightLastMove) {
8340             SetHighlights(-1, -1, toX, toY);
8341         }
8342     } else {
8343         fromX = moveList[currentMove][0] - AAA;
8344         fromY = moveList[currentMove][1] - ONE;
8345
8346         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8347
8348         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8349
8350         if (appData.highlightLastMove) {
8351             SetHighlights(fromX, fromY, toX, toY);
8352         }
8353     }
8354     DisplayMove(currentMove);
8355     SendMoveToProgram(currentMove++, &first);
8356     DisplayBothClocks();
8357     DrawPosition(FALSE, boards[currentMove]);
8358     // [HGM] PV info: always display, routine tests if empty
8359     DisplayComment(currentMove - 1, commentList[currentMove]);
8360     return TRUE;
8361 }
8362
8363
8364 int
8365 LoadGameOneMove(readAhead)
8366      ChessMove readAhead;
8367 {
8368     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8369     char promoChar = NULLCHAR;
8370     ChessMove moveType;
8371     char move[MSG_SIZ];
8372     char *p, *q;
8373
8374     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8375         gameMode != AnalyzeMode && gameMode != Training) {
8376         gameFileFP = NULL;
8377         return FALSE;
8378     }
8379
8380     yyboardindex = forwardMostMove;
8381     if (readAhead != (ChessMove)0) {
8382       moveType = readAhead;
8383     } else {
8384       if (gameFileFP == NULL)
8385           return FALSE;
8386       moveType = (ChessMove) yylex();
8387     }
8388
8389     done = FALSE;
8390     switch (moveType) {
8391       case Comment:
8392         if (appData.debugMode)
8393           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8394         p = yy_text;
8395         if (*p == '{' || *p == '[' || *p == '(') {
8396             p[strlen(p) - 1] = NULLCHAR;
8397             p++;
8398         }
8399
8400         /* append the comment but don't display it */
8401         while (*p == '\n') p++;
8402         AppendComment(currentMove, p);
8403         return TRUE;
8404
8405       case WhiteCapturesEnPassant:
8406       case BlackCapturesEnPassant:
8407       case WhitePromotionChancellor:
8408       case BlackPromotionChancellor:
8409       case WhitePromotionArchbishop:
8410       case BlackPromotionArchbishop:
8411       case WhitePromotionCentaur:
8412       case BlackPromotionCentaur:
8413       case WhitePromotionQueen:
8414       case BlackPromotionQueen:
8415       case WhitePromotionRook:
8416       case BlackPromotionRook:
8417       case WhitePromotionBishop:
8418       case BlackPromotionBishop:
8419       case WhitePromotionKnight:
8420       case BlackPromotionKnight:
8421       case WhitePromotionKing:
8422       case BlackPromotionKing:
8423       case NormalMove:
8424       case WhiteKingSideCastle:
8425       case WhiteQueenSideCastle:
8426       case BlackKingSideCastle:
8427       case BlackQueenSideCastle:
8428       case WhiteKingSideCastleWild:
8429       case WhiteQueenSideCastleWild:
8430       case BlackKingSideCastleWild:
8431       case BlackQueenSideCastleWild:
8432       /* PUSH Fabien */
8433       case WhiteHSideCastleFR:
8434       case WhiteASideCastleFR:
8435       case BlackHSideCastleFR:
8436       case BlackASideCastleFR:
8437       /* POP Fabien */
8438         if (appData.debugMode)
8439           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8440         fromX = currentMoveString[0] - AAA;
8441         fromY = currentMoveString[1] - ONE;
8442         toX = currentMoveString[2] - AAA;
8443         toY = currentMoveString[3] - ONE;
8444         promoChar = currentMoveString[4];
8445         break;
8446
8447       case WhiteDrop:
8448       case BlackDrop:
8449         if (appData.debugMode)
8450           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8451         fromX = moveType == WhiteDrop ?
8452           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8453         (int) CharToPiece(ToLower(currentMoveString[0]));
8454         fromY = DROP_RANK;
8455         toX = currentMoveString[2] - AAA;
8456         toY = currentMoveString[3] - ONE;
8457         break;
8458
8459       case WhiteWins:
8460       case BlackWins:
8461       case GameIsDrawn:
8462       case GameUnfinished:
8463         if (appData.debugMode)
8464           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8465         p = strchr(yy_text, '{');
8466         if (p == NULL) p = strchr(yy_text, '(');
8467         if (p == NULL) {
8468             p = yy_text;
8469             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8470         } else {
8471             q = strchr(p, *p == '{' ? '}' : ')');
8472             if (q != NULL) *q = NULLCHAR;
8473             p++;
8474         }
8475         GameEnds(moveType, p, GE_FILE);
8476         done = TRUE;
8477         if (cmailMsgLoaded) {
8478             ClearHighlights();
8479             flipView = WhiteOnMove(currentMove);
8480             if (moveType == GameUnfinished) flipView = !flipView;
8481             if (appData.debugMode)
8482               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8483         }
8484         break;
8485
8486       case (ChessMove) 0:       /* end of file */
8487         if (appData.debugMode)
8488           fprintf(debugFP, "Parser hit end of file\n");
8489         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8490                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8491           case MT_NONE:
8492           case MT_CHECK:
8493             break;
8494           case MT_CHECKMATE:
8495           case MT_STAINMATE:
8496             if (WhiteOnMove(currentMove)) {
8497                 GameEnds(BlackWins, "Black mates", GE_FILE);
8498             } else {
8499                 GameEnds(WhiteWins, "White mates", GE_FILE);
8500             }
8501             break;
8502           case MT_STALEMATE:
8503             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8504             break;
8505         }
8506         done = TRUE;
8507         break;
8508
8509       case MoveNumberOne:
8510         if (lastLoadGameStart == GNUChessGame) {
8511             /* GNUChessGames have numbers, but they aren't move numbers */
8512             if (appData.debugMode)
8513               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8514                       yy_text, (int) moveType);
8515             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8516         }
8517         /* else fall thru */
8518
8519       case XBoardGame:
8520       case GNUChessGame:
8521       case PGNTag:
8522         /* Reached start of next game in file */
8523         if (appData.debugMode)
8524           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8525         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8526                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8527           case MT_NONE:
8528           case MT_CHECK:
8529             break;
8530           case MT_CHECKMATE:
8531           case MT_STAINMATE:
8532             if (WhiteOnMove(currentMove)) {
8533                 GameEnds(BlackWins, "Black mates", GE_FILE);
8534             } else {
8535                 GameEnds(WhiteWins, "White mates", GE_FILE);
8536             }
8537             break;
8538           case MT_STALEMATE:
8539             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8540             break;
8541         }
8542         done = TRUE;
8543         break;
8544
8545       case PositionDiagram:     /* should not happen; ignore */
8546       case ElapsedTime:         /* ignore */
8547       case NAG:                 /* ignore */
8548         if (appData.debugMode)
8549           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8550                   yy_text, (int) moveType);
8551         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8552
8553       case IllegalMove:
8554         if (appData.testLegality) {
8555             if (appData.debugMode)
8556               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8557             sprintf(move, _("Illegal move: %d.%s%s"),
8558                     (forwardMostMove / 2) + 1,
8559                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8560             DisplayError(move, 0);
8561             done = TRUE;
8562         } else {
8563             if (appData.debugMode)
8564               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8565                       yy_text, currentMoveString);
8566             fromX = currentMoveString[0] - AAA;
8567             fromY = currentMoveString[1] - ONE;
8568             toX = currentMoveString[2] - AAA;
8569             toY = currentMoveString[3] - ONE;
8570             promoChar = currentMoveString[4];
8571         }
8572         break;
8573
8574       case AmbiguousMove:
8575         if (appData.debugMode)
8576           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8577         sprintf(move, _("Ambiguous move: %d.%s%s"),
8578                 (forwardMostMove / 2) + 1,
8579                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8580         DisplayError(move, 0);
8581         done = TRUE;
8582         break;
8583
8584       default:
8585       case ImpossibleMove:
8586         if (appData.debugMode)
8587           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8588         sprintf(move, _("Illegal move: %d.%s%s"),
8589                 (forwardMostMove / 2) + 1,
8590                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8591         DisplayError(move, 0);
8592         done = TRUE;
8593         break;
8594     }
8595
8596     if (done) {
8597         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8598             DrawPosition(FALSE, boards[currentMove]);
8599             DisplayBothClocks();
8600             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8601               DisplayComment(currentMove - 1, commentList[currentMove]);
8602         }
8603         (void) StopLoadGameTimer();
8604         gameFileFP = NULL;
8605         cmailOldMove = forwardMostMove;
8606         return FALSE;
8607     } else {
8608         /* currentMoveString is set as a side-effect of yylex */
8609         strcat(currentMoveString, "\n");
8610         strcpy(moveList[forwardMostMove], currentMoveString);
8611
8612         thinkOutput[0] = NULLCHAR;
8613         MakeMove(fromX, fromY, toX, toY, promoChar);
8614         currentMove = forwardMostMove;
8615         return TRUE;
8616     }
8617 }
8618
8619 /* Load the nth game from the given file */
8620 int
8621 LoadGameFromFile(filename, n, title, useList)
8622      char *filename;
8623      int n;
8624      char *title;
8625      /*Boolean*/ int useList;
8626 {
8627     FILE *f;
8628     char buf[MSG_SIZ];
8629
8630     if (strcmp(filename, "-") == 0) {
8631         f = stdin;
8632         title = "stdin";
8633     } else {
8634         f = fopen(filename, "rb");
8635         if (f == NULL) {
8636           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8637             DisplayError(buf, errno);
8638             return FALSE;
8639         }
8640     }
8641     if (fseek(f, 0, 0) == -1) {
8642         /* f is not seekable; probably a pipe */
8643         useList = FALSE;
8644     }
8645     if (useList && n == 0) {
8646         int error = GameListBuild(f);
8647         if (error) {
8648             DisplayError(_("Cannot build game list"), error);
8649         } else if (!ListEmpty(&gameList) &&
8650                    ((ListGame *) gameList.tailPred)->number > 1) {
8651             GameListPopUp(f, title);
8652             return TRUE;
8653         }
8654         GameListDestroy();
8655         n = 1;
8656     }
8657     if (n == 0) n = 1;
8658     return LoadGame(f, n, title, FALSE);
8659 }
8660
8661
8662 void
8663 MakeRegisteredMove()
8664 {
8665     int fromX, fromY, toX, toY;
8666     char promoChar;
8667     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8668         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8669           case CMAIL_MOVE:
8670           case CMAIL_DRAW:
8671             if (appData.debugMode)
8672               fprintf(debugFP, "Restoring %s for game %d\n",
8673                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8674
8675             thinkOutput[0] = NULLCHAR;
8676             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8677             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8678             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8679             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8680             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8681             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8682             MakeMove(fromX, fromY, toX, toY, promoChar);
8683             ShowMove(fromX, fromY, toX, toY);
8684
8685             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8686                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8687               case MT_NONE:
8688               case MT_CHECK:
8689                 break;
8690
8691               case MT_CHECKMATE:
8692               case MT_STAINMATE:
8693                 if (WhiteOnMove(currentMove)) {
8694                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8695                 } else {
8696                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8697                 }
8698                 break;
8699
8700               case MT_STALEMATE:
8701                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8702                 break;
8703             }
8704
8705             break;
8706
8707           case CMAIL_RESIGN:
8708             if (WhiteOnMove(currentMove)) {
8709                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8710             } else {
8711                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8712             }
8713             break;
8714
8715           case CMAIL_ACCEPT:
8716             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8717             break;
8718
8719           default:
8720             break;
8721         }
8722     }
8723
8724     return;
8725 }
8726
8727 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8728 int
8729 CmailLoadGame(f, gameNumber, title, useList)
8730      FILE *f;
8731      int gameNumber;
8732      char *title;
8733      int useList;
8734 {
8735     int retVal;
8736
8737     if (gameNumber > nCmailGames) {
8738         DisplayError(_("No more games in this message"), 0);
8739         return FALSE;
8740     }
8741     if (f == lastLoadGameFP) {
8742         int offset = gameNumber - lastLoadGameNumber;
8743         if (offset == 0) {
8744             cmailMsg[0] = NULLCHAR;
8745             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8746                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8747                 nCmailMovesRegistered--;
8748             }
8749             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8750             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8751                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8752             }
8753         } else {
8754             if (! RegisterMove()) return FALSE;
8755         }
8756     }
8757
8758     retVal = LoadGame(f, gameNumber, title, useList);
8759
8760     /* Make move registered during previous look at this game, if any */
8761     MakeRegisteredMove();
8762
8763     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8764         commentList[currentMove]
8765           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8766         DisplayComment(currentMove - 1, commentList[currentMove]);
8767     }
8768
8769     return retVal;
8770 }
8771
8772 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8773 int
8774 ReloadGame(offset)
8775      int offset;
8776 {
8777     int gameNumber = lastLoadGameNumber + offset;
8778     if (lastLoadGameFP == NULL) {
8779         DisplayError(_("No game has been loaded yet"), 0);
8780         return FALSE;
8781     }
8782     if (gameNumber <= 0) {
8783         DisplayError(_("Can't back up any further"), 0);
8784         return FALSE;
8785     }
8786     if (cmailMsgLoaded) {
8787         return CmailLoadGame(lastLoadGameFP, gameNumber,
8788                              lastLoadGameTitle, lastLoadGameUseList);
8789     } else {
8790         return LoadGame(lastLoadGameFP, gameNumber,
8791                         lastLoadGameTitle, lastLoadGameUseList);
8792     }
8793 }
8794
8795
8796
8797 /* Load the nth game from open file f */
8798 int
8799 LoadGame(f, gameNumber, title, useList)
8800      FILE *f;
8801      int gameNumber;
8802      char *title;
8803      int useList;
8804 {
8805     ChessMove cm;
8806     char buf[MSG_SIZ];
8807     int gn = gameNumber;
8808     ListGame *lg = NULL;
8809     int numPGNTags = 0;
8810     int err;
8811     GameMode oldGameMode;
8812     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8813
8814     if (appData.debugMode)
8815         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8816
8817     if (gameMode == Training )
8818         SetTrainingModeOff();
8819
8820     oldGameMode = gameMode;
8821     if (gameMode != BeginningOfGame) 
8822       {
8823         Reset(FALSE, TRUE);
8824       };
8825
8826     gameFileFP = f;
8827     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
8828       {
8829         fclose(lastLoadGameFP);
8830       };
8831
8832     if (useList) 
8833       {
8834         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8835         
8836         if (lg) 
8837           {
8838             fseek(f, lg->offset, 0);
8839             GameListHighlight(gameNumber);
8840             gn = 1;
8841           }
8842         else 
8843           {
8844             DisplayError(_("Game number out of range"), 0);
8845             return FALSE;
8846           };
8847       } 
8848     else 
8849       {
8850         GameListDestroy();
8851         if (fseek(f, 0, 0) == -1) 
8852           {
8853             if (f == lastLoadGameFP ?
8854                 gameNumber == lastLoadGameNumber + 1 :
8855                 gameNumber == 1) 
8856               {
8857                 gn = 1;
8858               } 
8859             else 
8860               {
8861                 DisplayError(_("Can't seek on game file"), 0);
8862                 return FALSE;
8863               };
8864           };
8865       };
8866
8867     lastLoadGameFP      = f;
8868     lastLoadGameNumber  = gameNumber;
8869     strcpy(lastLoadGameTitle, title);
8870     lastLoadGameUseList = useList;
8871
8872     yynewfile(f);
8873
8874     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
8875       {
8876         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8877                  lg->gameInfo.black);
8878         DisplayTitle(buf);
8879       } 
8880     else if (*title != NULLCHAR) 
8881       {
8882         if (gameNumber > 1) 
8883           {
8884             sprintf(buf, "%s %d", title, gameNumber);
8885             DisplayTitle(buf);
8886           } 
8887         else 
8888           {
8889             DisplayTitle(title);
8890           };
8891       };
8892
8893     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
8894       {
8895         gameMode = PlayFromGameFile;
8896         ModeHighlight();
8897       };
8898
8899     currentMove = forwardMostMove = backwardMostMove = 0;
8900     CopyBoard(boards[0], initialPosition);
8901     StopClocks();
8902
8903     /*
8904      * Skip the first gn-1 games in the file.
8905      * Also skip over anything that precedes an identifiable
8906      * start of game marker, to avoid being confused by
8907      * garbage at the start of the file.  Currently
8908      * recognized start of game markers are the move number "1",
8909      * the pattern "gnuchess .* game", the pattern
8910      * "^[#;%] [^ ]* game file", and a PGN tag block.
8911      * A game that starts with one of the latter two patterns
8912      * will also have a move number 1, possibly
8913      * following a position diagram.
8914      * 5-4-02: Let's try being more lenient and allowing a game to
8915      * start with an unnumbered move.  Does that break anything?
8916      */
8917     cm = lastLoadGameStart = (ChessMove) 0;
8918     while (gn > 0) {
8919         yyboardindex = forwardMostMove;
8920         cm = (ChessMove) yylex();
8921         switch (cm) {
8922           case (ChessMove) 0:
8923             if (cmailMsgLoaded) {
8924                 nCmailGames = CMAIL_MAX_GAMES - gn;
8925             } else {
8926                 Reset(TRUE, TRUE);
8927                 DisplayError(_("Game not found in file"), 0);
8928             }
8929             return FALSE;
8930
8931           case GNUChessGame:
8932           case XBoardGame:
8933             gn--;
8934             lastLoadGameStart = cm;
8935             break;
8936
8937           case MoveNumberOne:
8938             switch (lastLoadGameStart) {
8939               case GNUChessGame:
8940               case XBoardGame:
8941               case PGNTag:
8942                 break;
8943               case MoveNumberOne:
8944               case (ChessMove) 0:
8945                 gn--;           /* count this game */
8946                 lastLoadGameStart = cm;
8947                 break;
8948               default:
8949                 /* impossible */
8950                 break;
8951             }
8952             break;
8953
8954           case PGNTag:
8955             switch (lastLoadGameStart) {
8956               case GNUChessGame:
8957               case PGNTag:
8958               case MoveNumberOne:
8959               case (ChessMove) 0:
8960                 gn--;           /* count this game */
8961                 lastLoadGameStart = cm;
8962                 break;
8963               case XBoardGame:
8964                 lastLoadGameStart = cm; /* game counted already */
8965                 break;
8966               default:
8967                 /* impossible */
8968                 break;
8969             }
8970             if (gn > 0) {
8971                 do {
8972                     yyboardindex = forwardMostMove;
8973                     cm = (ChessMove) yylex();
8974                 } while (cm == PGNTag || cm == Comment);
8975             }
8976             break;
8977
8978           case WhiteWins:
8979           case BlackWins:
8980           case GameIsDrawn:
8981             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8982                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8983                     != CMAIL_OLD_RESULT) {
8984                     nCmailResults ++ ;
8985                     cmailResult[  CMAIL_MAX_GAMES
8986                                 - gn - 1] = CMAIL_OLD_RESULT;
8987                 }
8988             }
8989             break;
8990
8991           case NormalMove:
8992             /* Only a NormalMove can be at the start of a game
8993              * without a position diagram. */
8994             if (lastLoadGameStart == (ChessMove) 0) {
8995               gn--;
8996               lastLoadGameStart = MoveNumberOne;
8997             }
8998             break;
8999
9000           default:
9001             break;
9002         }
9003     }
9004
9005     if (appData.debugMode)
9006       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9007
9008     if (cm == XBoardGame) {
9009         /* Skip any header junk before position diagram and/or move 1 */
9010         for (;;) {
9011             yyboardindex = forwardMostMove;
9012             cm = (ChessMove) yylex();
9013
9014             if (cm == (ChessMove) 0 ||
9015                 cm == GNUChessGame || cm == XBoardGame) {
9016                 /* Empty game; pretend end-of-file and handle later */
9017                 cm = (ChessMove) 0;
9018                 break;
9019             }
9020
9021             if (cm == MoveNumberOne || cm == PositionDiagram ||
9022                 cm == PGNTag || cm == Comment)
9023               break;
9024         }
9025     } else if (cm == GNUChessGame) {
9026         if (gameInfo.event != NULL) {
9027             free(gameInfo.event);
9028         }
9029         gameInfo.event = StrSave(yy_text);
9030     }
9031
9032     startedFromSetupPosition = FALSE;
9033     while (cm == PGNTag) {
9034         if (appData.debugMode)
9035           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9036         err = ParsePGNTag(yy_text, &gameInfo);
9037         if (!err) numPGNTags++;
9038
9039         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9040         if(gameInfo.variant != oldVariant) {
9041             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9042             InitPosition(TRUE);
9043             oldVariant = gameInfo.variant;
9044             if (appData.debugMode)
9045               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9046         }
9047
9048
9049         if (gameInfo.fen != NULL) {
9050           Board initial_position;
9051           startedFromSetupPosition = TRUE;
9052           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9053             Reset(TRUE, TRUE);
9054             DisplayError(_("Bad FEN position in file"), 0);
9055             return FALSE;
9056           }
9057           CopyBoard(boards[0], initial_position);
9058           if (blackPlaysFirst) {
9059             currentMove = forwardMostMove = backwardMostMove = 1;
9060             CopyBoard(boards[1], initial_position);
9061             strcpy(moveList[0], "");
9062             strcpy(parseList[0], "");
9063             timeRemaining[0][1] = whiteTimeRemaining;
9064             timeRemaining[1][1] = blackTimeRemaining;
9065             if (commentList[0] != NULL) {
9066               commentList[1] = commentList[0];
9067               commentList[0] = NULL;
9068             }
9069           } else {
9070             currentMove = forwardMostMove = backwardMostMove = 0;
9071           }
9072           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9073           {   int i;
9074               initialRulePlies = FENrulePlies;
9075               epStatus[forwardMostMove] = FENepStatus;
9076               for( i=0; i< nrCastlingRights; i++ )
9077                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9078           }
9079           yyboardindex = forwardMostMove;
9080           free(gameInfo.fen);
9081           gameInfo.fen = NULL;
9082         }
9083
9084         yyboardindex = forwardMostMove;
9085         cm = (ChessMove) yylex();
9086
9087         /* Handle comments interspersed among the tags */
9088         while (cm == Comment) {
9089             char *p;
9090             if (appData.debugMode)
9091               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9092             p = yy_text;
9093             if (*p == '{' || *p == '[' || *p == '(') {
9094                 p[strlen(p) - 1] = NULLCHAR;
9095                 p++;
9096             }
9097             while (*p == '\n') p++;
9098             AppendComment(currentMove, p);
9099             yyboardindex = forwardMostMove;
9100             cm = (ChessMove) yylex();
9101         }
9102     }
9103
9104     /* don't rely on existence of Event tag since if game was
9105      * pasted from clipboard the Event tag may not exist
9106      */
9107     if (numPGNTags > 0){
9108         char *tags;
9109         if (gameInfo.variant == VariantNormal) {
9110           gameInfo.variant = StringToVariant(gameInfo.event);
9111         }
9112         if (!matchMode) {
9113           if( appData.autoDisplayTags ) {
9114             tags = PGNTags(&gameInfo);
9115             TagsPopUp(tags, CmailMsg());
9116             free(tags);
9117           }
9118         }
9119     } else {
9120         /* Make something up, but don't display it now */
9121         SetGameInfo();
9122         TagsPopDown();
9123     }
9124
9125     if (cm == PositionDiagram) {
9126         int i, j;
9127         char *p;
9128         Board initial_position;
9129
9130         if (appData.debugMode)
9131           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9132
9133         if (!startedFromSetupPosition) {
9134             p = yy_text;
9135             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9136               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9137                 switch (*p) {
9138                   case '[':
9139                   case '-':
9140                   case ' ':
9141                   case '\t':
9142                   case '\n':
9143                   case '\r':
9144                     break;
9145                   default:
9146                     initial_position[i][j++] = CharToPiece(*p);
9147                     break;
9148                 }
9149             while (*p == ' ' || *p == '\t' ||
9150                    *p == '\n' || *p == '\r') p++;
9151
9152             if (strncmp(p, "black", strlen("black"))==0)
9153               blackPlaysFirst = TRUE;
9154             else
9155               blackPlaysFirst = FALSE;
9156             startedFromSetupPosition = TRUE;
9157
9158             CopyBoard(boards[0], initial_position);
9159             if (blackPlaysFirst) {
9160                 currentMove = forwardMostMove = backwardMostMove = 1;
9161                 CopyBoard(boards[1], initial_position);
9162                 strcpy(moveList[0], "");
9163                 strcpy(parseList[0], "");
9164                 timeRemaining[0][1] = whiteTimeRemaining;
9165                 timeRemaining[1][1] = blackTimeRemaining;
9166                 if (commentList[0] != NULL) {
9167                     commentList[1] = commentList[0];
9168                     commentList[0] = NULL;
9169                 }
9170             } else {
9171                 currentMove = forwardMostMove = backwardMostMove = 0;
9172             }
9173         }
9174         yyboardindex = forwardMostMove;
9175         cm = (ChessMove) yylex();
9176     }
9177
9178     if (first.pr == NoProc) {
9179         StartChessProgram(&first);
9180     }
9181     InitChessProgram(&first, FALSE);
9182     SendToProgram("force\n", &first);
9183     if (startedFromSetupPosition) {
9184         SendBoard(&first, forwardMostMove);
9185     if (appData.debugMode) {
9186         fprintf(debugFP, "Load Game\n");
9187     }
9188         DisplayBothClocks();
9189     }
9190
9191     /* [HGM] server: flag to write setup moves in broadcast file as one */
9192     loadFlag = appData.suppressLoadMoves;
9193
9194     while (cm == Comment) {
9195         char *p;
9196         if (appData.debugMode)
9197           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9198         p = yy_text;
9199         if (*p == '{' || *p == '[' || *p == '(') {
9200             p[strlen(p) - 1] = NULLCHAR;
9201             p++;
9202         }
9203         while (*p == '\n') p++;
9204         AppendComment(currentMove, p);
9205         yyboardindex = forwardMostMove;
9206         cm = (ChessMove) yylex();
9207     }
9208
9209     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9210         cm == WhiteWins || cm == BlackWins ||
9211         cm == GameIsDrawn || cm == GameUnfinished) {
9212         DisplayMessage("", _("No moves in game"));
9213         if (cmailMsgLoaded) {
9214             if (appData.debugMode)
9215               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9216             ClearHighlights();
9217             flipView = FALSE;
9218         }
9219         DrawPosition(FALSE, boards[currentMove]);
9220         DisplayBothClocks();
9221         gameMode = EditGame;
9222         ModeHighlight();
9223         gameFileFP = NULL;
9224         cmailOldMove = 0;
9225         return TRUE;
9226     }
9227
9228     // [HGM] PV info: routine tests if comment empty
9229     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9230         DisplayComment(currentMove - 1, commentList[currentMove]);
9231     }
9232     if (!matchMode && appData.timeDelay != 0)
9233       DrawPosition(FALSE, boards[currentMove]);
9234
9235     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9236       programStats.ok_to_send = 1;
9237     }
9238
9239     /* if the first token after the PGN tags is a move
9240      * and not move number 1, retrieve it from the parser
9241      */
9242     if (cm != MoveNumberOne)
9243         LoadGameOneMove(cm);
9244
9245     /* load the remaining moves from the file */
9246     while (LoadGameOneMove((ChessMove)0)) {
9247       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9248       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9249     }
9250
9251     /* rewind to the start of the game */
9252     currentMove = backwardMostMove;
9253
9254     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9255
9256     if (oldGameMode == AnalyzeFile ||
9257         oldGameMode == AnalyzeMode) {
9258       AnalyzeFileEvent();
9259     }
9260
9261     if (matchMode || appData.timeDelay == 0) {
9262       ToEndEvent();
9263       gameMode = EditGame;
9264       ModeHighlight();
9265     } else if (appData.timeDelay > 0) {
9266       AutoPlayGameLoop();
9267     }
9268
9269     if (appData.debugMode)
9270         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9271
9272     loadFlag = 0; /* [HGM] true game starts */
9273     return TRUE;
9274 }
9275
9276 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9277 int
9278 ReloadPosition(offset)
9279      int offset;
9280 {
9281     int positionNumber = lastLoadPositionNumber + offset;
9282     if (lastLoadPositionFP == NULL) {
9283         DisplayError(_("No position has been loaded yet"), 0);
9284         return FALSE;
9285     }
9286     if (positionNumber <= 0) {
9287         DisplayError(_("Can't back up any further"), 0);
9288         return FALSE;
9289     }
9290     return LoadPosition(lastLoadPositionFP, positionNumber,
9291                         lastLoadPositionTitle);
9292 }
9293
9294 /* Load the nth position from the given file */
9295 int
9296 LoadPositionFromFile(filename, n, title)
9297      char *filename;
9298      int n;
9299      char *title;
9300 {
9301     FILE *f;
9302     char buf[MSG_SIZ];
9303
9304     if (strcmp(filename, "-") == 0) {
9305         return LoadPosition(stdin, n, "stdin");
9306     } else {
9307         f = fopen(filename, "rb");
9308         if (f == NULL) {
9309             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9310             DisplayError(buf, errno);
9311             return FALSE;
9312         } else {
9313             return LoadPosition(f, n, title);
9314         }
9315     }
9316 }
9317
9318 /* Load the nth position from the given open file, and close it */
9319 int
9320 LoadPosition(f, positionNumber, title)
9321      FILE *f;
9322      int positionNumber;
9323      char *title;
9324 {
9325     char *p, line[MSG_SIZ];
9326     Board initial_position;
9327     int i, j, fenMode, pn;
9328
9329     if (gameMode == Training )
9330         SetTrainingModeOff();
9331
9332     if (gameMode != BeginningOfGame) {
9333         Reset(FALSE, TRUE);
9334     }
9335     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9336         fclose(lastLoadPositionFP);
9337     }
9338     if (positionNumber == 0) positionNumber = 1;
9339     lastLoadPositionFP = f;
9340     lastLoadPositionNumber = positionNumber;
9341     strcpy(lastLoadPositionTitle, title);
9342     if (first.pr == NoProc) {
9343       StartChessProgram(&first);
9344       InitChessProgram(&first, FALSE);
9345     }
9346     pn = positionNumber;
9347     if (positionNumber < 0) {
9348         /* Negative position number means to seek to that byte offset */
9349         if (fseek(f, -positionNumber, 0) == -1) {
9350             DisplayError(_("Can't seek on position file"), 0);
9351             return FALSE;
9352         };
9353         pn = 1;
9354     } else {
9355         if (fseek(f, 0, 0) == -1) {
9356             if (f == lastLoadPositionFP ?
9357                 positionNumber == lastLoadPositionNumber + 1 :
9358                 positionNumber == 1) {
9359                 pn = 1;
9360             } else {
9361                 DisplayError(_("Can't seek on position file"), 0);
9362                 return FALSE;
9363             }
9364         }
9365     }
9366     /* See if this file is FEN or old-style xboard */
9367     if (fgets(line, MSG_SIZ, f) == NULL) {
9368         DisplayError(_("Position not found in file"), 0);
9369         return FALSE;
9370     }
9371     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9372     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9373
9374     if (pn >= 2) {
9375         if (fenMode || line[0] == '#') pn--;
9376         while (pn > 0) {
9377             /* skip positions before number pn */
9378             if (fgets(line, MSG_SIZ, f) == NULL) {
9379                 Reset(TRUE, TRUE);
9380                 DisplayError(_("Position not found in file"), 0);
9381                 return FALSE;
9382             }
9383             if (fenMode || line[0] == '#') pn--;
9384         }
9385     }
9386
9387     if (fenMode) {
9388         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9389             DisplayError(_("Bad FEN position in file"), 0);
9390             return FALSE;
9391         }
9392     } else {
9393         (void) fgets(line, MSG_SIZ, f);
9394         (void) fgets(line, MSG_SIZ, f);
9395
9396         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9397             (void) fgets(line, MSG_SIZ, f);
9398             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9399                 if (*p == ' ')
9400                   continue;
9401                 initial_position[i][j++] = CharToPiece(*p);
9402             }
9403         }
9404
9405         blackPlaysFirst = FALSE;
9406         if (!feof(f)) {
9407             (void) fgets(line, MSG_SIZ, f);
9408             if (strncmp(line, "black", strlen("black"))==0)
9409               blackPlaysFirst = TRUE;
9410         }
9411     }
9412     startedFromSetupPosition = TRUE;
9413
9414     SendToProgram("force\n", &first);
9415     CopyBoard(boards[0], initial_position);
9416     if (blackPlaysFirst) {
9417         currentMove = forwardMostMove = backwardMostMove = 1;
9418         strcpy(moveList[0], "");
9419         strcpy(parseList[0], "");
9420         CopyBoard(boards[1], initial_position);
9421         DisplayMessage("", _("Black to play"));
9422     } else {
9423         currentMove = forwardMostMove = backwardMostMove = 0;
9424         DisplayMessage("", _("White to play"));
9425     }
9426           /* [HGM] copy FEN attributes as well */
9427           {   int i;
9428               initialRulePlies = FENrulePlies;
9429               epStatus[forwardMostMove] = FENepStatus;
9430               for( i=0; i< nrCastlingRights; i++ )
9431                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9432           }
9433     SendBoard(&first, forwardMostMove);
9434     if (appData.debugMode) {
9435 int i, j;
9436   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9437   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9438         fprintf(debugFP, "Load Position\n");
9439     }
9440
9441     if (positionNumber > 1) {
9442         sprintf(line, "%s %d", title, positionNumber);
9443         DisplayTitle(line);
9444     } else {
9445         DisplayTitle(title);
9446     }
9447     gameMode = EditGame;
9448     ModeHighlight();
9449     ResetClocks();
9450     timeRemaining[0][1] = whiteTimeRemaining;
9451     timeRemaining[1][1] = blackTimeRemaining;
9452     DrawPosition(FALSE, boards[currentMove]);
9453
9454     return TRUE;
9455 }
9456
9457
9458 void
9459 CopyPlayerNameIntoFileName(dest, src)
9460      char **dest, *src;
9461 {
9462     while (*src != NULLCHAR && *src != ',') {
9463         if (*src == ' ') {
9464             *(*dest)++ = '_';
9465             src++;
9466         } else {
9467             *(*dest)++ = *src++;
9468         }
9469     }
9470 }
9471
9472 char *DefaultFileName(ext)
9473      char *ext;
9474 {
9475     static char def[MSG_SIZ];
9476     char *p;
9477
9478     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9479         p = def;
9480         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9481         *p++ = '-';
9482         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9483         *p++ = '.';
9484         strcpy(p, ext);
9485     } else {
9486         def[0] = NULLCHAR;
9487     }
9488     return def;
9489 }
9490
9491 /* Save the current game to the given file */
9492 int
9493 SaveGameToFile(filename, append)
9494      char *filename;
9495      int append;
9496 {
9497     FILE *f;
9498     char buf[MSG_SIZ];
9499
9500     if (strcmp(filename, "-") == 0) {
9501         return SaveGame(stdout, 0, NULL);
9502     } else {
9503         f = fopen(filename, append ? "a" : "w");
9504         if (f == NULL) {
9505             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9506             DisplayError(buf, errno);
9507             return FALSE;
9508         } else {
9509             return SaveGame(f, 0, NULL);
9510         }
9511     }
9512 }
9513
9514 char *
9515 SavePart(str)
9516      char *str;
9517 {
9518     static char buf[MSG_SIZ];
9519     char *p;
9520
9521     p = strchr(str, ' ');
9522     if (p == NULL) return str;
9523     strncpy(buf, str, p - str);
9524     buf[p - str] = NULLCHAR;
9525     return buf;
9526 }
9527
9528 #define PGN_MAX_LINE 75
9529
9530 #define PGN_SIDE_WHITE  0
9531 #define PGN_SIDE_BLACK  1
9532
9533 /* [AS] */
9534 static int FindFirstMoveOutOfBook( int side )
9535 {
9536     int result = -1;
9537
9538     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9539         int index = backwardMostMove;
9540         int has_book_hit = 0;
9541
9542         if( (index % 2) != side ) {
9543             index++;
9544         }
9545
9546         while( index < forwardMostMove ) {
9547             /* Check to see if engine is in book */
9548             int depth = pvInfoList[index].depth;
9549             int score = pvInfoList[index].score;
9550             int in_book = 0;
9551
9552             if( depth <= 2 ) {
9553                 in_book = 1;
9554             }
9555             else if( score == 0 && depth == 63 ) {
9556                 in_book = 1; /* Zappa */
9557             }
9558             else if( score == 2 && depth == 99 ) {
9559                 in_book = 1; /* Abrok */
9560             }
9561
9562             has_book_hit += in_book;
9563
9564             if( ! in_book ) {
9565                 result = index;
9566
9567                 break;
9568             }
9569
9570             index += 2;
9571         }
9572     }
9573
9574     return result;
9575 }
9576
9577 /* [AS] */
9578 void GetOutOfBookInfo( char * buf )
9579 {
9580     int oob[2];
9581     int i;
9582     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9583
9584     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9585     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9586
9587     *buf = '\0';
9588
9589     if( oob[0] >= 0 || oob[1] >= 0 ) {
9590         for( i=0; i<2; i++ ) {
9591             int idx = oob[i];
9592
9593             if( idx >= 0 ) {
9594                 if( i > 0 && oob[0] >= 0 ) {
9595                     strcat( buf, "   " );
9596                 }
9597
9598                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9599                 sprintf( buf+strlen(buf), "%s%.2f",
9600                     pvInfoList[idx].score >= 0 ? "+" : "",
9601                     pvInfoList[idx].score / 100.0 );
9602             }
9603         }
9604     }
9605 }
9606
9607 /* Save game in PGN style and close the file */
9608 int
9609 SaveGamePGN(f)
9610      FILE *f;
9611 {
9612     int i, offset, linelen, newblock;
9613     time_t tm;
9614 //    char *movetext;
9615     char numtext[32];
9616     int movelen, numlen, blank;
9617     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9618
9619     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9620
9621     tm = time((time_t *) NULL);
9622
9623     PrintPGNTags(f, &gameInfo);
9624
9625     if (backwardMostMove > 0 || startedFromSetupPosition) {
9626         char *fen = PositionToFEN(backwardMostMove, NULL);
9627         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9628         fprintf(f, "\n{--------------\n");
9629         PrintPosition(f, backwardMostMove);
9630         fprintf(f, "--------------}\n");
9631         free(fen);
9632     }
9633     else {
9634         /* [AS] Out of book annotation */
9635         if( appData.saveOutOfBookInfo ) {
9636             char buf[64];
9637
9638             GetOutOfBookInfo( buf );
9639
9640             if( buf[0] != '\0' ) {
9641                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9642             }
9643         }
9644
9645         fprintf(f, "\n");
9646     }
9647
9648     i = backwardMostMove;
9649     linelen = 0;
9650     newblock = TRUE;
9651
9652     while (i < forwardMostMove) {
9653         /* Print comments preceding this move */
9654         if (commentList[i] != NULL) {
9655             if (linelen > 0) fprintf(f, "\n");
9656             fprintf(f, "{\n%s}\n", commentList[i]);
9657             linelen = 0;
9658             newblock = TRUE;
9659         }
9660
9661         /* Format move number */
9662         if ((i % 2) == 0) {
9663             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9664         } else {
9665             if (newblock) {
9666                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9667             } else {
9668                 numtext[0] = NULLCHAR;
9669             }
9670         }
9671         numlen = strlen(numtext);
9672         newblock = FALSE;
9673
9674         /* Print move number */
9675         blank = linelen > 0 && numlen > 0;
9676         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9677             fprintf(f, "\n");
9678             linelen = 0;
9679             blank = 0;
9680         }
9681         if (blank) {
9682             fprintf(f, " ");
9683             linelen++;
9684         }
9685         fprintf(f, "%s", numtext);
9686         linelen += numlen;
9687
9688         /* Get move */
9689         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9690         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9691
9692         /* Print move */
9693         blank = linelen > 0 && movelen > 0;
9694         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9695             fprintf(f, "\n");
9696             linelen = 0;
9697             blank = 0;
9698         }
9699         if (blank) {
9700             fprintf(f, " ");
9701             linelen++;
9702         }
9703         fprintf(f, "%s", move_buffer);
9704         linelen += movelen;
9705
9706         /* [AS] Add PV info if present */
9707         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9708             /* [HGM] add time */
9709             char buf[MSG_SIZ]; int seconds = 0;
9710
9711             if(i >= backwardMostMove) {
9712                 if(WhiteOnMove(i))
9713                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9714                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9715                 else
9716                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9717                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9718             }
9719             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9720
9721             if( seconds <= 0) buf[0] = 0; else
9722             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9723                 seconds = (seconds + 4)/10; // round to full seconds
9724                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9725                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9726             }
9727
9728             sprintf( move_buffer, "{%s%.2f/%d%s}",
9729                 pvInfoList[i].score >= 0 ? "+" : "",
9730                 pvInfoList[i].score / 100.0,
9731                 pvInfoList[i].depth,
9732                 buf );
9733
9734             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9735
9736             /* Print score/depth */
9737             blank = linelen > 0 && movelen > 0;
9738             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9739                 fprintf(f, "\n");
9740                 linelen = 0;
9741                 blank = 0;
9742             }
9743             if (blank) {
9744                 fprintf(f, " ");
9745                 linelen++;
9746             }
9747             fprintf(f, "%s", move_buffer);
9748             linelen += movelen;
9749         }
9750
9751         i++;
9752     }
9753
9754     /* Start a new line */
9755     if (linelen > 0) fprintf(f, "\n");
9756
9757     /* Print comments after last move */
9758     if (commentList[i] != NULL) {
9759         fprintf(f, "{\n%s}\n", commentList[i]);
9760     }
9761
9762     /* Print result */
9763     if (gameInfo.resultDetails != NULL &&
9764         gameInfo.resultDetails[0] != NULLCHAR) {
9765         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9766                 PGNResult(gameInfo.result));
9767     } else {
9768         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9769     }
9770
9771     fclose(f);
9772     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9773     return TRUE;
9774 }
9775
9776 /* Save game in old style and close the file */
9777 int
9778 SaveGameOldStyle(f)
9779      FILE *f;
9780 {
9781     int i, offset;
9782     time_t tm;
9783
9784     tm = time((time_t *) NULL);
9785
9786     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9787     PrintOpponents(f);
9788
9789     if (backwardMostMove > 0 || startedFromSetupPosition) {
9790         fprintf(f, "\n[--------------\n");
9791         PrintPosition(f, backwardMostMove);
9792         fprintf(f, "--------------]\n");
9793     } else {
9794         fprintf(f, "\n");
9795     }
9796
9797     i = backwardMostMove;
9798     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9799
9800     while (i < forwardMostMove) {
9801         if (commentList[i] != NULL) {
9802             fprintf(f, "[%s]\n", commentList[i]);
9803         }
9804
9805         if ((i % 2) == 1) {
9806             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9807             i++;
9808         } else {
9809             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9810             i++;
9811             if (commentList[i] != NULL) {
9812                 fprintf(f, "\n");
9813                 continue;
9814             }
9815             if (i >= forwardMostMove) {
9816                 fprintf(f, "\n");
9817                 break;
9818             }
9819             fprintf(f, "%s\n", parseList[i]);
9820             i++;
9821         }
9822     }
9823
9824     if (commentList[i] != NULL) {
9825         fprintf(f, "[%s]\n", commentList[i]);
9826     }
9827
9828     /* This isn't really the old style, but it's close enough */
9829     if (gameInfo.resultDetails != NULL &&
9830         gameInfo.resultDetails[0] != NULLCHAR) {
9831         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9832                 gameInfo.resultDetails);
9833     } else {
9834         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9835     }
9836
9837     fclose(f);
9838     return TRUE;
9839 }
9840
9841 /* Save the current game to open file f and close the file */
9842 int
9843 SaveGame(f, dummy, dummy2)
9844      FILE *f;
9845      int dummy;
9846      char *dummy2;
9847 {
9848     if (gameMode == EditPosition) EditPositionDone();
9849     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9850     if (appData.oldSaveStyle)
9851       return SaveGameOldStyle(f);
9852     else
9853       return SaveGamePGN(f);
9854 }
9855
9856 /* Save the current position to the given file */
9857 int
9858 SavePositionToFile(filename)
9859      char *filename;
9860 {
9861     FILE *f;
9862     char buf[MSG_SIZ];
9863
9864     if (strcmp(filename, "-") == 0) {
9865         return SavePosition(stdout, 0, NULL);
9866     } else {
9867         f = fopen(filename, "a");
9868         if (f == NULL) {
9869             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9870             DisplayError(buf, errno);
9871             return FALSE;
9872         } else {
9873             SavePosition(f, 0, NULL);
9874             return TRUE;
9875         }
9876     }
9877 }
9878
9879 /* Save the current position to the given open file and close the file */
9880 int
9881 SavePosition(f, dummy, dummy2)
9882      FILE *f;
9883      int dummy;
9884      char *dummy2;
9885 {
9886     time_t tm;
9887     char *fen;
9888
9889     if (appData.oldSaveStyle) {
9890         tm = time((time_t *) NULL);
9891
9892         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9893         PrintOpponents(f);
9894         fprintf(f, "[--------------\n");
9895         PrintPosition(f, currentMove);
9896         fprintf(f, "--------------]\n");
9897     } else {
9898         fen = PositionToFEN(currentMove, NULL);
9899         fprintf(f, "%s\n", fen);
9900         free(fen);
9901     }
9902     fclose(f);
9903     return TRUE;
9904 }
9905
9906 void
9907 ReloadCmailMsgEvent(unregister)
9908      int unregister;
9909 {
9910 #if !WIN32
9911     static char *inFilename = NULL;
9912     static char *outFilename;
9913     int i;
9914     struct stat inbuf, outbuf;
9915     int status;
9916
9917     /* Any registered moves are unregistered if unregister is set, */
9918     /* i.e. invoked by the signal handler */
9919     if (unregister) {
9920         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9921             cmailMoveRegistered[i] = FALSE;
9922             if (cmailCommentList[i] != NULL) {
9923                 free(cmailCommentList[i]);
9924                 cmailCommentList[i] = NULL;
9925             }
9926         }
9927         nCmailMovesRegistered = 0;
9928     }
9929
9930     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9931         cmailResult[i] = CMAIL_NOT_RESULT;
9932     }
9933     nCmailResults = 0;
9934
9935     if (inFilename == NULL) {
9936         /* Because the filenames are static they only get malloced once  */
9937         /* and they never get freed                                      */
9938         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9939         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9940
9941         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9942         sprintf(outFilename, "%s.out", appData.cmailGameName);
9943     }
9944
9945     status = stat(outFilename, &outbuf);
9946     if (status < 0) {
9947         cmailMailedMove = FALSE;
9948     } else {
9949         status = stat(inFilename, &inbuf);
9950         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9951     }
9952
9953     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9954        counts the games, notes how each one terminated, etc.
9955
9956        It would be nice to remove this kludge and instead gather all
9957        the information while building the game list.  (And to keep it
9958        in the game list nodes instead of having a bunch of fixed-size
9959        parallel arrays.)  Note this will require getting each game's
9960        termination from the PGN tags, as the game list builder does
9961        not process the game moves.  --mann
9962        */
9963     cmailMsgLoaded = TRUE;
9964     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9965
9966     /* Load first game in the file or popup game menu */
9967     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9968
9969 #endif /* !WIN32 */
9970     return;
9971 }
9972
9973 int
9974 RegisterMove()
9975 {
9976     FILE *f;
9977     char string[MSG_SIZ];
9978
9979     if (   cmailMailedMove
9980         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9981         return TRUE;            /* Allow free viewing  */
9982     }
9983
9984     /* Unregister move to ensure that we don't leave RegisterMove        */
9985     /* with the move registered when the conditions for registering no   */
9986     /* longer hold                                                       */
9987     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9988         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9989         nCmailMovesRegistered --;
9990
9991         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9992           {
9993               free(cmailCommentList[lastLoadGameNumber - 1]);
9994               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9995           }
9996     }
9997
9998     if (cmailOldMove == -1) {
9999         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10000         return FALSE;
10001     }
10002
10003     if (currentMove > cmailOldMove + 1) {
10004         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10005         return FALSE;
10006     }
10007
10008     if (currentMove < cmailOldMove) {
10009         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10010         return FALSE;
10011     }
10012
10013     if (forwardMostMove > currentMove) {
10014         /* Silently truncate extra moves */
10015         TruncateGame();
10016     }
10017
10018     if (   (currentMove == cmailOldMove + 1)
10019         || (   (currentMove == cmailOldMove)
10020             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10021                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10022         if (gameInfo.result != GameUnfinished) {
10023             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10024         }
10025
10026         if (commentList[currentMove] != NULL) {
10027             cmailCommentList[lastLoadGameNumber - 1]
10028               = StrSave(commentList[currentMove]);
10029         }
10030         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10031
10032         if (appData.debugMode)
10033           fprintf(debugFP, "Saving %s for game %d\n",
10034                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10035
10036         sprintf(string,
10037                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10038
10039         f = fopen(string, "w");
10040         if (appData.oldSaveStyle) {
10041             SaveGameOldStyle(f); /* also closes the file */
10042
10043             sprintf(string, "%s.pos.out", appData.cmailGameName);
10044             f = fopen(string, "w");
10045             SavePosition(f, 0, NULL); /* also closes the file */
10046         } else {
10047             fprintf(f, "{--------------\n");
10048             PrintPosition(f, currentMove);
10049             fprintf(f, "--------------}\n\n");
10050
10051             SaveGame(f, 0, NULL); /* also closes the file*/
10052         }
10053
10054         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10055         nCmailMovesRegistered ++;
10056     } else if (nCmailGames == 1) {
10057         DisplayError(_("You have not made a move yet"), 0);
10058         return FALSE;
10059     }
10060
10061     return TRUE;
10062 }
10063
10064 void
10065 MailMoveEvent()
10066 {
10067 #if !WIN32
10068     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10069     FILE *commandOutput;
10070     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10071     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10072     int nBuffers;
10073     int i;
10074     int archived;
10075     char *arcDir;
10076
10077     if (! cmailMsgLoaded) {
10078         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10079         return;
10080     }
10081
10082     if (nCmailGames == nCmailResults) {
10083         DisplayError(_("No unfinished games"), 0);
10084         return;
10085     }
10086
10087 #if CMAIL_PROHIBIT_REMAIL
10088     if (cmailMailedMove) {
10089         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);
10090         DisplayError(msg, 0);
10091         return;
10092     }
10093 #endif
10094
10095     if (! (cmailMailedMove || RegisterMove())) return;
10096
10097     if (   cmailMailedMove
10098         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10099         sprintf(string, partCommandString,
10100                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10101         commandOutput = popen(string, "r");
10102
10103         if (commandOutput == NULL) {
10104             DisplayError(_("Failed to invoke cmail"), 0);
10105         } else {
10106             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10107                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10108             }
10109             if (nBuffers > 1) {
10110                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10111                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10112                 nBytes = MSG_SIZ - 1;
10113             } else {
10114                 (void) memcpy(msg, buffer, nBytes);
10115             }
10116             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10117
10118             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10119                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10120
10121                 archived = TRUE;
10122                 for (i = 0; i < nCmailGames; i ++) {
10123                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10124                         archived = FALSE;
10125                     }
10126                 }
10127                 if (   archived
10128                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10129                         != NULL)) {
10130                     sprintf(buffer, "%s/%s.%s.archive",
10131                             arcDir,
10132                             appData.cmailGameName,
10133                             gameInfo.date);
10134                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10135                     cmailMsgLoaded = FALSE;
10136                 }
10137             }
10138
10139             DisplayInformation(msg);
10140             pclose(commandOutput);
10141         }
10142     } else {
10143         if ((*cmailMsg) != '\0') {
10144             DisplayInformation(cmailMsg);
10145         }
10146     }
10147
10148     return;
10149 #endif /* !WIN32 */
10150 }
10151
10152 char *
10153 CmailMsg()
10154 {
10155 #if WIN32
10156     return NULL;
10157 #else
10158     int  prependComma = 0;
10159     char number[5];
10160     char string[MSG_SIZ];       /* Space for game-list */
10161     int  i;
10162
10163     if (!cmailMsgLoaded) return "";
10164
10165     if (cmailMailedMove) {
10166         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10167     } else {
10168         /* Create a list of games left */
10169         sprintf(string, "[");
10170         for (i = 0; i < nCmailGames; i ++) {
10171             if (! (   cmailMoveRegistered[i]
10172                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10173                 if (prependComma) {
10174                     sprintf(number, ",%d", i + 1);
10175                 } else {
10176                     sprintf(number, "%d", i + 1);
10177                     prependComma = 1;
10178                 }
10179
10180                 strcat(string, number);
10181             }
10182         }
10183         strcat(string, "]");
10184
10185         if (nCmailMovesRegistered + nCmailResults == 0) {
10186             switch (nCmailGames) {
10187               case 1:
10188                 sprintf(cmailMsg,
10189                         _("Still need to make move for game\n"));
10190                 break;
10191
10192               case 2:
10193                 sprintf(cmailMsg,
10194                         _("Still need to make moves for both games\n"));
10195                 break;
10196
10197               default:
10198                 sprintf(cmailMsg,
10199                         _("Still need to make moves for all %d games\n"),
10200                         nCmailGames);
10201                 break;
10202             }
10203         } else {
10204             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10205               case 1:
10206                 sprintf(cmailMsg,
10207                         _("Still need to make a move for game %s\n"),
10208                         string);
10209                 break;
10210
10211               case 0:
10212                 if (nCmailResults == nCmailGames) {
10213                     sprintf(cmailMsg, _("No unfinished games\n"));
10214                 } else {
10215                     sprintf(cmailMsg, _("Ready to send mail\n"));
10216                 }
10217                 break;
10218
10219               default:
10220                 sprintf(cmailMsg,
10221                         _("Still need to make moves for games %s\n"),
10222                         string);
10223             }
10224         }
10225     }
10226     return cmailMsg;
10227 #endif /* WIN32 */
10228 }
10229
10230 void
10231 ResetGameEvent()
10232 {
10233     if (gameMode == Training)
10234       SetTrainingModeOff();
10235
10236     Reset(TRUE, TRUE);
10237     cmailMsgLoaded = FALSE;
10238     if (appData.icsActive) {
10239       SendToICS(ics_prefix);
10240       SendToICS("refresh\n");
10241     }
10242 }
10243
10244 void
10245 ExitEvent(status)
10246      int status;
10247 {
10248     exiting++;
10249     if (exiting > 2) {
10250       /* Give up on clean exit */
10251       exit(status);
10252     }
10253     if (exiting > 1) {
10254       /* Keep trying for clean exit */
10255       return;
10256     }
10257
10258     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10259
10260     if (telnetISR != NULL) {
10261       RemoveInputSource(telnetISR);
10262     }
10263     if (icsPR != NoProc) {
10264       DestroyChildProcess(icsPR, TRUE);
10265     }
10266
10267     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10268     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10269
10270     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10271     /* make sure this other one finishes before killing it!                  */
10272     if(endingGame) { int count = 0;
10273         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10274         while(endingGame && count++ < 10) DoSleep(1);
10275         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10276     }
10277
10278     /* Kill off chess programs */
10279     if (first.pr != NoProc) {
10280         ExitAnalyzeMode();
10281
10282         DoSleep( appData.delayBeforeQuit );
10283         SendToProgram("quit\n", &first);
10284         DoSleep( appData.delayAfterQuit );
10285         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10286     }
10287     if (second.pr != NoProc) {
10288         DoSleep( appData.delayBeforeQuit );
10289         SendToProgram("quit\n", &second);
10290         DoSleep( appData.delayAfterQuit );
10291         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10292     }
10293     if (first.isr != NULL) {
10294         RemoveInputSource(first.isr);
10295     }
10296     if (second.isr != NULL) {
10297         RemoveInputSource(second.isr);
10298     }
10299
10300     ShutDownFrontEnd();
10301     exit(status);
10302 }
10303
10304 void
10305 PauseEvent()
10306 {
10307     if (appData.debugMode)
10308         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10309     if (pausing) {
10310         pausing = FALSE;
10311         ModeHighlight();
10312         if (gameMode == MachinePlaysWhite ||
10313             gameMode == MachinePlaysBlack) {
10314             StartClocks();
10315         } else {
10316             DisplayBothClocks();
10317         }
10318         if (gameMode == PlayFromGameFile) {
10319             if (appData.timeDelay >= 0)
10320                 AutoPlayGameLoop();
10321         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10322             Reset(FALSE, TRUE);
10323             SendToICS(ics_prefix);
10324             SendToICS("refresh\n");
10325         } else if (currentMove < forwardMostMove) {
10326             ForwardInner(forwardMostMove);
10327         }
10328         pauseExamInvalid = FALSE;
10329     } else {
10330         switch (gameMode) {
10331           default:
10332             return;
10333           case IcsExamining:
10334             pauseExamForwardMostMove = forwardMostMove;
10335             pauseExamInvalid = FALSE;
10336             /* fall through */
10337           case IcsObserving:
10338           case IcsPlayingWhite:
10339           case IcsPlayingBlack:
10340             pausing = TRUE;
10341             ModeHighlight();
10342             return;
10343           case PlayFromGameFile:
10344             (void) StopLoadGameTimer();
10345             pausing = TRUE;
10346             ModeHighlight();
10347             break;
10348           case BeginningOfGame:
10349             if (appData.icsActive) return;
10350             /* else fall through */
10351           case MachinePlaysWhite:
10352           case MachinePlaysBlack:
10353           case TwoMachinesPlay:
10354             if (forwardMostMove == 0)
10355               return;           /* don't pause if no one has moved */
10356             if ((gameMode == MachinePlaysWhite &&
10357                  !WhiteOnMove(forwardMostMove)) ||
10358                 (gameMode == MachinePlaysBlack &&
10359                  WhiteOnMove(forwardMostMove))) {
10360                 StopClocks();
10361             }
10362             pausing = TRUE;
10363             ModeHighlight();
10364             break;
10365         }
10366     }
10367 }
10368
10369 void
10370 EditCommentEvent()
10371 {
10372     char title[MSG_SIZ];
10373
10374     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10375         strcpy(title, _("Edit comment"));
10376     } else {
10377         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10378                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10379                 parseList[currentMove - 1]);
10380     }
10381
10382     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10383 }
10384
10385
10386 void
10387 EditTagsEvent()
10388 {
10389     char *tags = PGNTags(&gameInfo);
10390     EditTagsPopUp(tags);
10391     free(tags);
10392 }
10393
10394 void
10395 AnalyzeModeEvent()
10396 {
10397     if (appData.noChessProgram || gameMode == AnalyzeMode)
10398       return;
10399
10400     if (gameMode != AnalyzeFile) {
10401         if (!appData.icsEngineAnalyze) {
10402                EditGameEvent();
10403                if (gameMode != EditGame) return;
10404         }
10405         ResurrectChessProgram();
10406         SendToProgram("analyze\n", &first);
10407         first.analyzing = TRUE;
10408         /*first.maybeThinking = TRUE;*/
10409         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10410         EngineOutputPopUp();
10411     }
10412     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10413     pausing = FALSE;
10414     ModeHighlight();
10415     SetGameInfo();
10416
10417     StartAnalysisClock();
10418     GetTimeMark(&lastNodeCountTime);
10419     lastNodeCount = 0;
10420 }
10421
10422 void
10423 AnalyzeFileEvent()
10424 {
10425     if (appData.noChessProgram || gameMode == AnalyzeFile)
10426       return;
10427
10428     if (gameMode != AnalyzeMode) {
10429         EditGameEvent();
10430         if (gameMode != EditGame) return;
10431         ResurrectChessProgram();
10432         SendToProgram("analyze\n", &first);
10433         first.analyzing = TRUE;
10434         /*first.maybeThinking = TRUE;*/
10435         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10436         EngineOutputPopUp();
10437     }
10438     gameMode = AnalyzeFile;
10439     pausing = FALSE;
10440     ModeHighlight();
10441     SetGameInfo();
10442
10443     StartAnalysisClock();
10444     GetTimeMark(&lastNodeCountTime);
10445     lastNodeCount = 0;
10446 }
10447
10448 void
10449 MachineWhiteEvent()
10450 {
10451     char buf[MSG_SIZ];
10452     char *bookHit = NULL;
10453
10454     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10455       return;
10456
10457
10458     if (gameMode == PlayFromGameFile ||
10459         gameMode == TwoMachinesPlay  ||
10460         gameMode == Training         ||
10461         gameMode == AnalyzeMode      ||
10462         gameMode == EndOfGame)
10463         EditGameEvent();
10464
10465     if (gameMode == EditPosition)
10466         EditPositionDone();
10467
10468     if (!WhiteOnMove(currentMove)) {
10469         DisplayError(_("It is not White's turn"), 0);
10470         return;
10471     }
10472
10473     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10474       ExitAnalyzeMode();
10475
10476     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10477         gameMode == AnalyzeFile)
10478         TruncateGame();
10479
10480     ResurrectChessProgram();    /* in case it isn't running */
10481     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10482         gameMode = MachinePlaysWhite;
10483         ResetClocks();
10484     } else
10485     gameMode = MachinePlaysWhite;
10486     pausing = FALSE;
10487     ModeHighlight();
10488     SetGameInfo();
10489     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10490     DisplayTitle(buf);
10491     if (first.sendName) {
10492       sprintf(buf, "name %s\n", gameInfo.black);
10493       SendToProgram(buf, &first);
10494     }
10495     if (first.sendTime) {
10496       if (first.useColors) {
10497         SendToProgram("black\n", &first); /*gnu kludge*/
10498       }
10499       SendTimeRemaining(&first, TRUE);
10500     }
10501     if (first.useColors) {
10502       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10503     }
10504     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10505     SetMachineThinkingEnables();
10506     first.maybeThinking = TRUE;
10507     StartClocks();
10508     firstMove = FALSE;
10509
10510     if (appData.autoFlipView && !flipView) {
10511       flipView = !flipView;
10512       DrawPosition(FALSE, NULL);
10513       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10514     }
10515
10516     if(bookHit) { // [HGM] book: simulate book reply
10517         static char bookMove[MSG_SIZ]; // a bit generous?
10518
10519         programStats.nodes = programStats.depth = programStats.time =
10520         programStats.score = programStats.got_only_move = 0;
10521         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10522
10523         strcpy(bookMove, "move ");
10524         strcat(bookMove, bookHit);
10525         HandleMachineMove(bookMove, &first);
10526     }
10527 }
10528
10529 void
10530 MachineBlackEvent()
10531 {
10532     char buf[MSG_SIZ];
10533    char *bookHit = NULL;
10534
10535     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10536         return;
10537
10538
10539     if (gameMode == PlayFromGameFile ||
10540         gameMode == TwoMachinesPlay  ||
10541         gameMode == Training         ||
10542         gameMode == AnalyzeMode      ||
10543         gameMode == EndOfGame)
10544         EditGameEvent();
10545
10546     if (gameMode == EditPosition)
10547         EditPositionDone();
10548
10549     if (WhiteOnMove(currentMove)) {
10550         DisplayError(_("It is not Black's turn"), 0);
10551         return;
10552     }
10553
10554     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10555       ExitAnalyzeMode();
10556
10557     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10558         gameMode == AnalyzeFile)
10559         TruncateGame();
10560
10561     ResurrectChessProgram();    /* in case it isn't running */
10562     gameMode = MachinePlaysBlack;
10563     pausing = FALSE;
10564     ModeHighlight();
10565     SetGameInfo();
10566     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10567     DisplayTitle(buf);
10568     if (first.sendName) {
10569       sprintf(buf, "name %s\n", gameInfo.white);
10570       SendToProgram(buf, &first);
10571     }
10572     if (first.sendTime) {
10573       if (first.useColors) {
10574         SendToProgram("white\n", &first); /*gnu kludge*/
10575       }
10576       SendTimeRemaining(&first, FALSE);
10577     }
10578     if (first.useColors) {
10579       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10580     }
10581     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10582     SetMachineThinkingEnables();
10583     first.maybeThinking = TRUE;
10584     StartClocks();
10585
10586     if (appData.autoFlipView && flipView) {
10587       flipView = !flipView;
10588       DrawPosition(FALSE, NULL);
10589       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10590     }
10591     if(bookHit) { // [HGM] book: simulate book reply
10592         static char bookMove[MSG_SIZ]; // a bit generous?
10593
10594         programStats.nodes = programStats.depth = programStats.time =
10595         programStats.score = programStats.got_only_move = 0;
10596         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10597
10598         strcpy(bookMove, "move ");
10599         strcat(bookMove, bookHit);
10600         HandleMachineMove(bookMove, &first);
10601     }
10602 }
10603
10604
10605 void
10606 DisplayTwoMachinesTitle()
10607 {
10608     char buf[MSG_SIZ];
10609     if (appData.matchGames > 0) {
10610         if (first.twoMachinesColor[0] == 'w') {
10611             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10612                     gameInfo.white, gameInfo.black,
10613                     first.matchWins, second.matchWins,
10614                     matchGame - 1 - (first.matchWins + second.matchWins));
10615         } else {
10616             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10617                     gameInfo.white, gameInfo.black,
10618                     second.matchWins, first.matchWins,
10619                     matchGame - 1 - (first.matchWins + second.matchWins));
10620         }
10621     } else {
10622         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10623     }
10624     DisplayTitle(buf);
10625 }
10626
10627 void
10628 TwoMachinesEvent P((void))
10629 {
10630     int i;
10631     char buf[MSG_SIZ];
10632     ChessProgramState *onmove;
10633     char *bookHit = NULL;
10634
10635     if (appData.noChessProgram) return;
10636
10637     switch (gameMode) {
10638       case TwoMachinesPlay:
10639         return;
10640       case MachinePlaysWhite:
10641       case MachinePlaysBlack:
10642         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10643             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10644             return;
10645         }
10646         /* fall through */
10647       case BeginningOfGame:
10648       case PlayFromGameFile:
10649       case EndOfGame:
10650         EditGameEvent();
10651         if (gameMode != EditGame) return;
10652         break;
10653       case EditPosition:
10654         EditPositionDone();
10655         break;
10656       case AnalyzeMode:
10657       case AnalyzeFile:
10658         ExitAnalyzeMode();
10659         break;
10660       case EditGame:
10661       default:
10662         break;
10663     }
10664
10665     forwardMostMove = currentMove;
10666     ResurrectChessProgram();    /* in case first program isn't running */
10667
10668     if (second.pr == NULL) {
10669         StartChessProgram(&second);
10670         if (second.protocolVersion == 1) {
10671           TwoMachinesEventIfReady();
10672         } else {
10673           /* kludge: allow timeout for initial "feature" command */
10674           FreezeUI();
10675           DisplayMessage("", _("Starting second chess program"));
10676           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10677         }
10678         return;
10679     }
10680     DisplayMessage("", "");
10681     InitChessProgram(&second, FALSE);
10682     SendToProgram("force\n", &second);
10683     if (startedFromSetupPosition) {
10684         SendBoard(&second, backwardMostMove);
10685     if (appData.debugMode) {
10686         fprintf(debugFP, "Two Machines\n");
10687     }
10688     }
10689     for (i = backwardMostMove; i < forwardMostMove; i++) {
10690         SendMoveToProgram(i, &second);
10691     }
10692
10693     gameMode = TwoMachinesPlay;
10694     pausing = FALSE;
10695     ModeHighlight();
10696     SetGameInfo();
10697     DisplayTwoMachinesTitle();
10698     firstMove = TRUE;
10699     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10700         onmove = &first;
10701     } else {
10702         onmove = &second;
10703     }
10704
10705     SendToProgram(first.computerString, &first);
10706     if (first.sendName) {
10707       sprintf(buf, "name %s\n", second.tidy);
10708       SendToProgram(buf, &first);
10709     }
10710     SendToProgram(second.computerString, &second);
10711     if (second.sendName) {
10712       sprintf(buf, "name %s\n", first.tidy);
10713       SendToProgram(buf, &second);
10714     }
10715
10716     ResetClocks();
10717     if (!first.sendTime || !second.sendTime) {
10718         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10719         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10720     }
10721     if (onmove->sendTime) {
10722       if (onmove->useColors) {
10723         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10724       }
10725       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10726     }
10727     if (onmove->useColors) {
10728       SendToProgram(onmove->twoMachinesColor, onmove);
10729     }
10730     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10731 //    SendToProgram("go\n", onmove);
10732     onmove->maybeThinking = TRUE;
10733     SetMachineThinkingEnables();
10734
10735     StartClocks();
10736
10737     if(bookHit) { // [HGM] book: simulate book reply
10738         static char bookMove[MSG_SIZ]; // a bit generous?
10739
10740         programStats.nodes = programStats.depth = programStats.time =
10741         programStats.score = programStats.got_only_move = 0;
10742         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10743
10744         strcpy(bookMove, "move ");
10745         strcat(bookMove, bookHit);
10746         HandleMachineMove(bookMove, &first);
10747     }
10748 }
10749
10750 void
10751 TrainingEvent()
10752 {
10753     if (gameMode == Training) {
10754       SetTrainingModeOff();
10755       gameMode = PlayFromGameFile;
10756       DisplayMessage("", _("Training mode off"));
10757     } else {
10758       gameMode = Training;
10759       animateTraining = appData.animate;
10760
10761       /* make sure we are not already at the end of the game */
10762       if (currentMove < forwardMostMove) {
10763         SetTrainingModeOn();
10764         DisplayMessage("", _("Training mode on"));
10765       } else {
10766         gameMode = PlayFromGameFile;
10767         DisplayError(_("Already at end of game"), 0);
10768       }
10769     }
10770     ModeHighlight();
10771 }
10772
10773 void
10774 IcsClientEvent()
10775 {
10776     if (!appData.icsActive) return;
10777     switch (gameMode) {
10778       case IcsPlayingWhite:
10779       case IcsPlayingBlack:
10780       case IcsObserving:
10781       case IcsIdle:
10782       case BeginningOfGame:
10783       case IcsExamining:
10784         return;
10785
10786       case EditGame:
10787         break;
10788
10789       case EditPosition:
10790         EditPositionDone();
10791         break;
10792
10793       case AnalyzeMode:
10794       case AnalyzeFile:
10795         ExitAnalyzeMode();
10796         break;
10797
10798       default:
10799         EditGameEvent();
10800         break;
10801     }
10802
10803     gameMode = IcsIdle;
10804     ModeHighlight();
10805     return;
10806 }
10807
10808
10809 void
10810 EditGameEvent()
10811 {
10812     int i;
10813
10814     switch (gameMode) {
10815       case Training:
10816         SetTrainingModeOff();
10817         break;
10818       case MachinePlaysWhite:
10819       case MachinePlaysBlack:
10820       case BeginningOfGame:
10821         SendToProgram("force\n", &first);
10822         SetUserThinkingEnables();
10823         break;
10824       case PlayFromGameFile:
10825         (void) StopLoadGameTimer();
10826         if (gameFileFP != NULL) {
10827             gameFileFP = NULL;
10828         }
10829         break;
10830       case EditPosition:
10831         EditPositionDone();
10832         break;
10833       case AnalyzeMode:
10834       case AnalyzeFile:
10835         ExitAnalyzeMode();
10836         SendToProgram("force\n", &first);
10837         break;
10838       case TwoMachinesPlay:
10839         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10840         ResurrectChessProgram();
10841         SetUserThinkingEnables();
10842         break;
10843       case EndOfGame:
10844         ResurrectChessProgram();
10845         break;
10846       case IcsPlayingBlack:
10847       case IcsPlayingWhite:
10848         DisplayError(_("Warning: You are still playing a game"), 0);
10849         break;
10850       case IcsObserving:
10851         DisplayError(_("Warning: You are still observing a game"), 0);
10852         break;
10853       case IcsExamining:
10854         DisplayError(_("Warning: You are still examining a game"), 0);
10855         break;
10856       case IcsIdle:
10857         break;
10858       case EditGame:
10859       default:
10860         return;
10861     }
10862
10863     pausing = FALSE;
10864     StopClocks();
10865     first.offeredDraw = second.offeredDraw = 0;
10866
10867     if (gameMode == PlayFromGameFile) {
10868         whiteTimeRemaining = timeRemaining[0][currentMove];
10869         blackTimeRemaining = timeRemaining[1][currentMove];
10870         DisplayTitle("");
10871     }
10872
10873     if (gameMode == MachinePlaysWhite ||
10874         gameMode == MachinePlaysBlack ||
10875         gameMode == TwoMachinesPlay ||
10876         gameMode == EndOfGame) {
10877         i = forwardMostMove;
10878         while (i > currentMove) {
10879             SendToProgram("undo\n", &first);
10880             i--;
10881         }
10882         whiteTimeRemaining = timeRemaining[0][currentMove];
10883         blackTimeRemaining = timeRemaining[1][currentMove];
10884         DisplayBothClocks();
10885         if (whiteFlag || blackFlag) {
10886             whiteFlag = blackFlag = 0;
10887         }
10888         DisplayTitle("");
10889     }
10890
10891     gameMode = EditGame;
10892     ModeHighlight();
10893     SetGameInfo();
10894 }
10895
10896
10897 void
10898 EditPositionEvent()
10899 {
10900     if (gameMode == EditPosition) {
10901         EditGameEvent();
10902         return;
10903     }
10904
10905     EditGameEvent();
10906     if (gameMode != EditGame) return;
10907
10908     gameMode = EditPosition;
10909     ModeHighlight();
10910     SetGameInfo();
10911     if (currentMove > 0)
10912       CopyBoard(boards[0], boards[currentMove]);
10913
10914     blackPlaysFirst = !WhiteOnMove(currentMove);
10915     ResetClocks();
10916     currentMove = forwardMostMove = backwardMostMove = 0;
10917     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10918     DisplayMove(-1);
10919 }
10920
10921 void
10922 ExitAnalyzeMode()
10923 {
10924     /* [DM] icsEngineAnalyze - possible call from other functions */
10925     if (appData.icsEngineAnalyze) {
10926         appData.icsEngineAnalyze = FALSE;
10927
10928         DisplayMessage("",_("Close ICS engine analyze..."));
10929     }
10930     if (first.analysisSupport && first.analyzing) {
10931       SendToProgram("exit\n", &first);
10932       first.analyzing = FALSE;
10933     }
10934     EngineOutputPopDown();
10935     thinkOutput[0] = NULLCHAR;
10936 }
10937
10938 void
10939 EditPositionDone()
10940 {
10941     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10942
10943     startedFromSetupPosition = TRUE;
10944     InitChessProgram(&first, FALSE);
10945     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10946     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10947         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10948         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10949     } else castlingRights[0][2] = -1;
10950     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10951         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10952         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10953     } else castlingRights[0][5] = -1;
10954     SendToProgram("force\n", &first);
10955     if (blackPlaysFirst) {
10956         strcpy(moveList[0], "");
10957         strcpy(parseList[0], "");
10958         currentMove = forwardMostMove = backwardMostMove = 1;
10959         CopyBoard(boards[1], boards[0]);
10960         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10961         { int i;
10962           epStatus[1] = epStatus[0];
10963           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10964         }
10965     } else {
10966         currentMove = forwardMostMove = backwardMostMove = 0;
10967     }
10968     SendBoard(&first, forwardMostMove);
10969     if (appData.debugMode) {
10970         fprintf(debugFP, "EditPosDone\n");
10971     }
10972     DisplayTitle("");
10973     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10974     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10975     gameMode = EditGame;
10976     ModeHighlight();
10977     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10978     ClearHighlights(); /* [AS] */
10979 }
10980
10981 /* Pause for `ms' milliseconds */
10982 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10983 void
10984 TimeDelay(ms)
10985      long ms;
10986 {
10987     TimeMark m1, m2;
10988
10989     GetTimeMark(&m1);
10990     do {
10991         GetTimeMark(&m2);
10992     } while (SubtractTimeMarks(&m2, &m1) < ms);
10993 }
10994
10995 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10996 void
10997 SendMultiLineToICS(buf)
10998      char *buf;
10999 {
11000     char temp[MSG_SIZ+1], *p;
11001     int len;
11002
11003     len = strlen(buf);
11004     if (len > MSG_SIZ)
11005       len = MSG_SIZ;
11006
11007     strncpy(temp, buf, len);
11008     temp[len] = 0;
11009
11010     p = temp;
11011     while (*p) {
11012         if (*p == '\n' || *p == '\r')
11013           *p = ' ';
11014         ++p;
11015     }
11016
11017     strcat(temp, "\n");
11018     SendToICS(temp);
11019     SendToPlayer(temp, strlen(temp));
11020 }
11021
11022 void
11023 SetWhiteToPlayEvent()
11024 {
11025     if (gameMode == EditPosition) {
11026         blackPlaysFirst = FALSE;
11027         DisplayBothClocks();    /* works because currentMove is 0 */
11028     } else if (gameMode == IcsExamining) {
11029         SendToICS(ics_prefix);
11030         SendToICS("tomove white\n");
11031     }
11032 }
11033
11034 void
11035 SetBlackToPlayEvent()
11036 {
11037     if (gameMode == EditPosition) {
11038         blackPlaysFirst = TRUE;
11039         currentMove = 1;        /* kludge */
11040         DisplayBothClocks();
11041         currentMove = 0;
11042     } else if (gameMode == IcsExamining) {
11043         SendToICS(ics_prefix);
11044         SendToICS("tomove black\n");
11045     }
11046 }
11047
11048 void
11049 EditPositionMenuEvent(selection, x, y)
11050      ChessSquare selection;
11051      int x, y;
11052 {
11053     char buf[MSG_SIZ];
11054     ChessSquare piece = boards[0][y][x];
11055
11056     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11057
11058     switch (selection) {
11059       case ClearBoard:
11060         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11061             SendToICS(ics_prefix);
11062             SendToICS("bsetup clear\n");
11063         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11064             SendToICS(ics_prefix);
11065             SendToICS("clearboard\n");
11066         } else {
11067             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11068                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11069                 for (y = 0; y < BOARD_HEIGHT; y++) {
11070                     if (gameMode == IcsExamining) {
11071                         if (boards[currentMove][y][x] != EmptySquare) {
11072                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11073                                     AAA + x, ONE + y);
11074                             SendToICS(buf);
11075                         }
11076                     } else {
11077                         boards[0][y][x] = p;
11078                     }
11079                 }
11080             }
11081         }
11082         if (gameMode == EditPosition) {
11083             DrawPosition(FALSE, boards[0]);
11084         }
11085         break;
11086
11087       case WhitePlay:
11088         SetWhiteToPlayEvent();
11089         break;
11090
11091       case BlackPlay:
11092         SetBlackToPlayEvent();
11093         break;
11094
11095       case EmptySquare:
11096         if (gameMode == IcsExamining) {
11097             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11098             SendToICS(buf);
11099         } else {
11100             boards[0][y][x] = EmptySquare;
11101             DrawPosition(FALSE, boards[0]);
11102         }
11103         break;
11104
11105       case PromotePiece:
11106         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11107            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11108             selection = (ChessSquare) (PROMOTED piece);
11109         } else if(piece == EmptySquare) selection = WhiteSilver;
11110         else selection = (ChessSquare)((int)piece - 1);
11111         goto defaultlabel;
11112
11113       case DemotePiece:
11114         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11115            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11116             selection = (ChessSquare) (DEMOTED piece);
11117         } else if(piece == EmptySquare) selection = BlackSilver;
11118         else selection = (ChessSquare)((int)piece + 1);
11119         goto defaultlabel;
11120
11121       case WhiteQueen:
11122       case BlackQueen:
11123         if(gameInfo.variant == VariantShatranj ||
11124            gameInfo.variant == VariantXiangqi  ||
11125            gameInfo.variant == VariantCourier    )
11126             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11127         goto defaultlabel;
11128
11129       case WhiteKing:
11130       case BlackKing:
11131         if(gameInfo.variant == VariantXiangqi)
11132             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11133         if(gameInfo.variant == VariantKnightmate)
11134             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11135       default:
11136         defaultlabel:
11137         if (gameMode == IcsExamining) {
11138             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11139                     PieceToChar(selection), AAA + x, ONE + y);
11140             SendToICS(buf);
11141         } else {
11142             boards[0][y][x] = selection;
11143             DrawPosition(FALSE, boards[0]);
11144         }
11145         break;
11146     }
11147 }
11148
11149
11150 void
11151 DropMenuEvent(selection, x, y)
11152      ChessSquare selection;
11153      int x, y;
11154 {
11155     ChessMove moveType;
11156
11157     switch (gameMode) {
11158       case IcsPlayingWhite:
11159       case MachinePlaysBlack:
11160         if (!WhiteOnMove(currentMove)) {
11161             DisplayMoveError(_("It is Black's turn"));
11162             return;
11163         }
11164         moveType = WhiteDrop;
11165         break;
11166       case IcsPlayingBlack:
11167       case MachinePlaysWhite:
11168         if (WhiteOnMove(currentMove)) {
11169             DisplayMoveError(_("It is White's turn"));
11170             return;
11171         }
11172         moveType = BlackDrop;
11173         break;
11174       case EditGame:
11175         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11176         break;
11177       default:
11178         return;
11179     }
11180
11181     if (moveType == BlackDrop && selection < BlackPawn) {
11182       selection = (ChessSquare) ((int) selection
11183                                  + (int) BlackPawn - (int) WhitePawn);
11184     }
11185     if (boards[currentMove][y][x] != EmptySquare) {
11186         DisplayMoveError(_("That square is occupied"));
11187         return;
11188     }
11189
11190     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11191 }
11192
11193 void
11194 AcceptEvent()
11195 {
11196     /* Accept a pending offer of any kind from opponent */
11197
11198     if (appData.icsActive) {
11199         SendToICS(ics_prefix);
11200         SendToICS("accept\n");
11201     } else if (cmailMsgLoaded) {
11202         if (currentMove == cmailOldMove &&
11203             commentList[cmailOldMove] != NULL &&
11204             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11205                    "Black offers a draw" : "White offers a draw")) {
11206             TruncateGame();
11207             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11208             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11209         } else {
11210             DisplayError(_("There is no pending offer on this move"), 0);
11211             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11212         }
11213     } else {
11214         /* Not used for offers from chess program */
11215     }
11216 }
11217
11218 void
11219 DeclineEvent()
11220 {
11221     /* Decline a pending offer of any kind from opponent */
11222
11223     if (appData.icsActive) {
11224         SendToICS(ics_prefix);
11225         SendToICS("decline\n");
11226     } else if (cmailMsgLoaded) {
11227         if (currentMove == cmailOldMove &&
11228             commentList[cmailOldMove] != NULL &&
11229             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11230                    "Black offers a draw" : "White offers a draw")) {
11231 #ifdef NOTDEF
11232             AppendComment(cmailOldMove, "Draw declined");
11233             DisplayComment(cmailOldMove - 1, "Draw declined");
11234 #endif /*NOTDEF*/
11235         } else {
11236             DisplayError(_("There is no pending offer on this move"), 0);
11237         }
11238     } else {
11239         /* Not used for offers from chess program */
11240     }
11241 }
11242
11243 void
11244 RematchEvent()
11245 {
11246     /* Issue ICS rematch command */
11247     if (appData.icsActive) {
11248         SendToICS(ics_prefix);
11249         SendToICS("rematch\n");
11250     }
11251 }
11252
11253 void
11254 CallFlagEvent()
11255 {
11256     /* Call your opponent's flag (claim a win on time) */
11257     if (appData.icsActive) {
11258         SendToICS(ics_prefix);
11259         SendToICS("flag\n");
11260     } else {
11261         switch (gameMode) {
11262           default:
11263             return;
11264           case MachinePlaysWhite:
11265             if (whiteFlag) {
11266                 if (blackFlag)
11267                   GameEnds(GameIsDrawn, "Both players ran out of time",
11268                            GE_PLAYER);
11269                 else
11270                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11271             } else {
11272                 DisplayError(_("Your opponent is not out of time"), 0);
11273             }
11274             break;
11275           case MachinePlaysBlack:
11276             if (blackFlag) {
11277                 if (whiteFlag)
11278                   GameEnds(GameIsDrawn, "Both players ran out of time",
11279                            GE_PLAYER);
11280                 else
11281                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11282             } else {
11283                 DisplayError(_("Your opponent is not out of time"), 0);
11284             }
11285             break;
11286         }
11287     }
11288 }
11289
11290 void
11291 DrawEvent()
11292 {
11293     /* Offer draw or accept pending draw offer from opponent */
11294
11295     if (appData.icsActive) {
11296         /* Note: tournament rules require draw offers to be
11297            made after you make your move but before you punch
11298            your clock.  Currently ICS doesn't let you do that;
11299            instead, you immediately punch your clock after making
11300            a move, but you can offer a draw at any time. */
11301
11302         SendToICS(ics_prefix);
11303         SendToICS("draw\n");
11304     } else if (cmailMsgLoaded) {
11305         if (currentMove == cmailOldMove &&
11306             commentList[cmailOldMove] != NULL &&
11307             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11308                    "Black offers a draw" : "White offers a draw")) {
11309             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11310             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11311         } else if (currentMove == cmailOldMove + 1) {
11312             char *offer = WhiteOnMove(cmailOldMove) ?
11313               "White offers a draw" : "Black offers a draw";
11314             AppendComment(currentMove, offer);
11315             DisplayComment(currentMove - 1, offer);
11316             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11317         } else {
11318             DisplayError(_("You must make your move before offering a draw"), 0);
11319             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11320         }
11321     } else if (first.offeredDraw) {
11322         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11323     } else {
11324         if (first.sendDrawOffers) {
11325             SendToProgram("draw\n", &first);
11326             userOfferedDraw = TRUE;
11327         }
11328     }
11329 }
11330
11331 void
11332 AdjournEvent()
11333 {
11334     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11335
11336     if (appData.icsActive) {
11337         SendToICS(ics_prefix);
11338         SendToICS("adjourn\n");
11339     } else {
11340         /* Currently GNU Chess doesn't offer or accept Adjourns */
11341     }
11342 }
11343
11344
11345 void
11346 AbortEvent()
11347 {
11348     /* Offer Abort or accept pending Abort offer from opponent */
11349
11350     if (appData.icsActive) {
11351         SendToICS(ics_prefix);
11352         SendToICS("abort\n");
11353     } else {
11354         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11355     }
11356 }
11357
11358 void
11359 ResignEvent()
11360 {
11361     /* Resign.  You can do this even if it's not your turn. */
11362
11363     if (appData.icsActive) {
11364         SendToICS(ics_prefix);
11365         SendToICS("resign\n");
11366     } else {
11367         switch (gameMode) {
11368           case MachinePlaysWhite:
11369             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11370             break;
11371           case MachinePlaysBlack:
11372             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11373             break;
11374           case EditGame:
11375             if (cmailMsgLoaded) {
11376                 TruncateGame();
11377                 if (WhiteOnMove(cmailOldMove)) {
11378                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11379                 } else {
11380                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11381                 }
11382                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11383             }
11384             break;
11385           default:
11386             break;
11387         }
11388     }
11389 }
11390
11391
11392 void
11393 StopObservingEvent()
11394 {
11395     /* Stop observing current games */
11396     SendToICS(ics_prefix);
11397     SendToICS("unobserve\n");
11398 }
11399
11400 void
11401 StopExaminingEvent()
11402 {
11403     /* Stop observing current game */
11404     SendToICS(ics_prefix);
11405     SendToICS("unexamine\n");
11406 }
11407
11408 void
11409 ForwardInner(target)
11410      int target;
11411 {
11412     int limit;
11413
11414     if (appData.debugMode)
11415         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11416                 target, currentMove, forwardMostMove);
11417
11418     if (gameMode == EditPosition)
11419       return;
11420
11421     if (gameMode == PlayFromGameFile && !pausing)
11422       PauseEvent();
11423
11424     if (gameMode == IcsExamining && pausing)
11425       limit = pauseExamForwardMostMove;
11426     else
11427       limit = forwardMostMove;
11428
11429     if (target > limit) target = limit;
11430
11431     if (target > 0 && moveList[target - 1][0]) {
11432         int fromX, fromY, toX, toY;
11433         toX = moveList[target - 1][2] - AAA;
11434         toY = moveList[target - 1][3] - ONE;
11435         if (moveList[target - 1][1] == '@') {
11436             if (appData.highlightLastMove) {
11437                 SetHighlights(-1, -1, toX, toY);
11438             }
11439         } else {
11440             fromX = moveList[target - 1][0] - AAA;
11441             fromY = moveList[target - 1][1] - ONE;
11442             if (target == currentMove + 1) {
11443                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11444             }
11445             if (appData.highlightLastMove) {
11446                 SetHighlights(fromX, fromY, toX, toY);
11447             }
11448         }
11449     }
11450     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11451         gameMode == Training || gameMode == PlayFromGameFile ||
11452         gameMode == AnalyzeFile) {
11453         while (currentMove < target) {
11454             SendMoveToProgram(currentMove++, &first);
11455         }
11456     } else {
11457         currentMove = target;
11458     }
11459
11460     if (gameMode == EditGame || gameMode == EndOfGame) {
11461         whiteTimeRemaining = timeRemaining[0][currentMove];
11462         blackTimeRemaining = timeRemaining[1][currentMove];
11463     }
11464     DisplayBothClocks();
11465     DisplayMove(currentMove - 1);
11466     DrawPosition(FALSE, boards[currentMove]);
11467     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11468     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11469         DisplayComment(currentMove - 1, commentList[currentMove]);
11470     }
11471 }
11472
11473
11474 void
11475 ForwardEvent()
11476 {
11477     if (gameMode == IcsExamining && !pausing) {
11478         SendToICS(ics_prefix);
11479         SendToICS("forward\n");
11480     } else {
11481         ForwardInner(currentMove + 1);
11482     }
11483 }
11484
11485 void
11486 ToEndEvent()
11487 {
11488     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11489         /* to optimze, we temporarily turn off analysis mode while we feed
11490          * the remaining moves to the engine. Otherwise we get analysis output
11491          * after each move.
11492          */
11493         if (first.analysisSupport) {
11494           SendToProgram("exit\nforce\n", &first);
11495           first.analyzing = FALSE;
11496         }
11497     }
11498
11499     if (gameMode == IcsExamining && !pausing) {
11500         SendToICS(ics_prefix);
11501         SendToICS("forward 999999\n");
11502     } else {
11503         ForwardInner(forwardMostMove);
11504     }
11505
11506     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11507         /* we have fed all the moves, so reactivate analysis mode */
11508         SendToProgram("analyze\n", &first);
11509         first.analyzing = TRUE;
11510         /*first.maybeThinking = TRUE;*/
11511         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11512     }
11513 }
11514
11515 void
11516 BackwardInner(target)
11517      int target;
11518 {
11519     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11520
11521     if (appData.debugMode)
11522         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11523                 target, currentMove, forwardMostMove);
11524
11525     if (gameMode == EditPosition) return;
11526     if (currentMove <= backwardMostMove) {
11527         ClearHighlights();
11528         DrawPosition(full_redraw, boards[currentMove]);
11529         return;
11530     }
11531     if (gameMode == PlayFromGameFile && !pausing)
11532       PauseEvent();
11533
11534     if (moveList[target][0]) {
11535         int fromX, fromY, toX, toY;
11536         toX = moveList[target][2] - AAA;
11537         toY = moveList[target][3] - ONE;
11538         if (moveList[target][1] == '@') {
11539             if (appData.highlightLastMove) {
11540                 SetHighlights(-1, -1, toX, toY);
11541             }
11542         } else {
11543             fromX = moveList[target][0] - AAA;
11544             fromY = moveList[target][1] - ONE;
11545             if (target == currentMove - 1) {
11546                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11547             }
11548             if (appData.highlightLastMove) {
11549                 SetHighlights(fromX, fromY, toX, toY);
11550             }
11551         }
11552     }
11553     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11554         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11555         while (currentMove > target) {
11556             SendToProgram("undo\n", &first);
11557             currentMove--;
11558         }
11559     } else {
11560         currentMove = target;
11561     }
11562
11563     if (gameMode == EditGame || gameMode == EndOfGame) {
11564         whiteTimeRemaining = timeRemaining[0][currentMove];
11565         blackTimeRemaining = timeRemaining[1][currentMove];
11566     }
11567     DisplayBothClocks();
11568     DisplayMove(currentMove - 1);
11569     DrawPosition(full_redraw, boards[currentMove]);
11570     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11571     // [HGM] PV info: routine tests if comment empty
11572     DisplayComment(currentMove - 1, commentList[currentMove]);
11573 }
11574
11575 void
11576 BackwardEvent()
11577 {
11578     if (gameMode == IcsExamining && !pausing) {
11579         SendToICS(ics_prefix);
11580         SendToICS("backward\n");
11581     } else {
11582         BackwardInner(currentMove - 1);
11583     }
11584 }
11585
11586 void
11587 ToStartEvent()
11588 {
11589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11590         /* to optimze, we temporarily turn off analysis mode while we undo
11591          * all the moves. Otherwise we get analysis output after each undo.
11592          */
11593         if (first.analysisSupport) {
11594           SendToProgram("exit\nforce\n", &first);
11595           first.analyzing = FALSE;
11596         }
11597     }
11598
11599     if (gameMode == IcsExamining && !pausing) {
11600         SendToICS(ics_prefix);
11601         SendToICS("backward 999999\n");
11602     } else {
11603         BackwardInner(backwardMostMove);
11604     }
11605
11606     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11607         /* we have fed all the moves, so reactivate analysis mode */
11608         SendToProgram("analyze\n", &first);
11609         first.analyzing = TRUE;
11610         /*first.maybeThinking = TRUE;*/
11611         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11612     }
11613 }
11614
11615 void
11616 ToNrEvent(int to)
11617 {
11618   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11619   if (to >= forwardMostMove) to = forwardMostMove;
11620   if (to <= backwardMostMove) to = backwardMostMove;
11621   if (to < currentMove) {
11622     BackwardInner(to);
11623   } else {
11624     ForwardInner(to);
11625   }
11626 }
11627
11628 void
11629 RevertEvent()
11630 {
11631     if (gameMode != IcsExamining) {
11632         DisplayError(_("You are not examining a game"), 0);
11633         return;
11634     }
11635     if (pausing) {
11636         DisplayError(_("You can't revert while pausing"), 0);
11637         return;
11638     }
11639     SendToICS(ics_prefix);
11640     SendToICS("revert\n");
11641 }
11642
11643 void
11644 RetractMoveEvent()
11645 {
11646     switch (gameMode) {
11647       case MachinePlaysWhite:
11648       case MachinePlaysBlack:
11649         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11650             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11651             return;
11652         }
11653         if (forwardMostMove < 2) return;
11654         currentMove = forwardMostMove = forwardMostMove - 2;
11655         whiteTimeRemaining = timeRemaining[0][currentMove];
11656         blackTimeRemaining = timeRemaining[1][currentMove];
11657         DisplayBothClocks();
11658         DisplayMove(currentMove - 1);
11659         ClearHighlights();/*!! could figure this out*/
11660         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11661         SendToProgram("remove\n", &first);
11662         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11663         break;
11664
11665       case BeginningOfGame:
11666       default:
11667         break;
11668
11669       case IcsPlayingWhite:
11670       case IcsPlayingBlack:
11671         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11672             SendToICS(ics_prefix);
11673             SendToICS("takeback 2\n");
11674         } else {
11675             SendToICS(ics_prefix);
11676             SendToICS("takeback 1\n");
11677         }
11678         break;
11679     }
11680 }
11681
11682 void
11683 MoveNowEvent()
11684 {
11685     ChessProgramState *cps;
11686
11687     switch (gameMode) {
11688       case MachinePlaysWhite:
11689         if (!WhiteOnMove(forwardMostMove)) {
11690             DisplayError(_("It is your turn"), 0);
11691             return;
11692         }
11693         cps = &first;
11694         break;
11695       case MachinePlaysBlack:
11696         if (WhiteOnMove(forwardMostMove)) {
11697             DisplayError(_("It is your turn"), 0);
11698             return;
11699         }
11700         cps = &first;
11701         break;
11702       case TwoMachinesPlay:
11703         if (WhiteOnMove(forwardMostMove) ==
11704             (first.twoMachinesColor[0] == 'w')) {
11705             cps = &first;
11706         } else {
11707             cps = &second;
11708         }
11709         break;
11710       case BeginningOfGame:
11711       default:
11712         return;
11713     }
11714     SendToProgram("?\n", cps);
11715 }
11716
11717 void
11718 TruncateGameEvent()
11719 {
11720     EditGameEvent();
11721     if (gameMode != EditGame) return;
11722     TruncateGame();
11723 }
11724
11725 void
11726 TruncateGame()
11727 {
11728     if (forwardMostMove > currentMove) {
11729         if (gameInfo.resultDetails != NULL) {
11730             free(gameInfo.resultDetails);
11731             gameInfo.resultDetails = NULL;
11732             gameInfo.result = GameUnfinished;
11733         }
11734         forwardMostMove = currentMove;
11735         HistorySet(parseList, backwardMostMove, forwardMostMove,
11736                    currentMove-1);
11737     }
11738 }
11739
11740 void
11741 HintEvent()
11742 {
11743     if (appData.noChessProgram) return;
11744     switch (gameMode) {
11745       case MachinePlaysWhite:
11746         if (WhiteOnMove(forwardMostMove)) {
11747             DisplayError(_("Wait until your turn"), 0);
11748             return;
11749         }
11750         break;
11751       case BeginningOfGame:
11752       case MachinePlaysBlack:
11753         if (!WhiteOnMove(forwardMostMove)) {
11754             DisplayError(_("Wait until your turn"), 0);
11755             return;
11756         }
11757         break;
11758       default:
11759         DisplayError(_("No hint available"), 0);
11760         return;
11761     }
11762     SendToProgram("hint\n", &first);
11763     hintRequested = TRUE;
11764 }
11765
11766 void
11767 BookEvent()
11768 {
11769     if (appData.noChessProgram) return;
11770     switch (gameMode) {
11771       case MachinePlaysWhite:
11772         if (WhiteOnMove(forwardMostMove)) {
11773             DisplayError(_("Wait until your turn"), 0);
11774             return;
11775         }
11776         break;
11777       case BeginningOfGame:
11778       case MachinePlaysBlack:
11779         if (!WhiteOnMove(forwardMostMove)) {
11780             DisplayError(_("Wait until your turn"), 0);
11781             return;
11782         }
11783         break;
11784       case EditPosition:
11785         EditPositionDone();
11786         break;
11787       case TwoMachinesPlay:
11788         return;
11789       default:
11790         break;
11791     }
11792     SendToProgram("bk\n", &first);
11793     bookOutput[0] = NULLCHAR;
11794     bookRequested = TRUE;
11795 }
11796
11797 void
11798 AboutGameEvent()
11799 {
11800     char *tags = PGNTags(&gameInfo);
11801     TagsPopUp(tags, CmailMsg());
11802     free(tags);
11803 }
11804
11805 /* end button procedures */
11806
11807 void
11808 PrintPosition(fp, move)
11809      FILE *fp;
11810      int move;
11811 {
11812     int i, j;
11813
11814     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11815         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11816             char c = PieceToChar(boards[move][i][j]);
11817             fputc(c == 'x' ? '.' : c, fp);
11818             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11819         }
11820     }
11821     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11822       fprintf(fp, "white to play\n");
11823     else
11824       fprintf(fp, "black to play\n");
11825 }
11826
11827 void
11828 PrintOpponents(fp)
11829      FILE *fp;
11830 {
11831     if (gameInfo.white != NULL) {
11832         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11833     } else {
11834         fprintf(fp, "\n");
11835     }
11836 }
11837
11838 /* Find last component of program's own name, using some heuristics */
11839 void
11840 TidyProgramName(prog, host, buf)
11841      char *prog, *host, buf[MSG_SIZ];
11842 {
11843     char *p, *q;
11844     int local = (strcmp(host, "localhost") == 0);
11845     while (!local && (p = strchr(prog, ';')) != NULL) {
11846         p++;
11847         while (*p == ' ') p++;
11848         prog = p;
11849     }
11850     if (*prog == '"' || *prog == '\'') {
11851         q = strchr(prog + 1, *prog);
11852     } else {
11853         q = strchr(prog, ' ');
11854     }
11855     if (q == NULL) q = prog + strlen(prog);
11856     p = q;
11857     while (p >= prog && *p != '/' && *p != '\\') p--;
11858     p++;
11859     if(p == prog && *p == '"') p++;
11860     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11861     memcpy(buf, p, q - p);
11862     buf[q - p] = NULLCHAR;
11863     if (!local) {
11864         strcat(buf, "@");
11865         strcat(buf, host);
11866     }
11867 }
11868
11869 char *
11870 TimeControlTagValue()
11871 {
11872     char buf[MSG_SIZ];
11873     if (!appData.clockMode) {
11874         strcpy(buf, "-");
11875     } else if (movesPerSession > 0) {
11876         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11877     } else if (timeIncrement == 0) {
11878         sprintf(buf, "%ld", timeControl/1000);
11879     } else {
11880         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11881     }
11882     return StrSave(buf);
11883 }
11884
11885 void
11886 SetGameInfo()
11887 {
11888     /* This routine is used only for certain modes */
11889     VariantClass v = gameInfo.variant;
11890     ClearGameInfo(&gameInfo);
11891     gameInfo.variant = v;
11892
11893     switch (gameMode) {
11894       case MachinePlaysWhite:
11895         gameInfo.event = StrSave( appData.pgnEventHeader );
11896         gameInfo.site = StrSave(HostName());
11897         gameInfo.date = PGNDate();
11898         gameInfo.round = StrSave("-");
11899         gameInfo.white = StrSave(first.tidy);
11900         gameInfo.black = StrSave(UserName());
11901         gameInfo.timeControl = TimeControlTagValue();
11902         break;
11903
11904       case MachinePlaysBlack:
11905         gameInfo.event = StrSave( appData.pgnEventHeader );
11906         gameInfo.site = StrSave(HostName());
11907         gameInfo.date = PGNDate();
11908         gameInfo.round = StrSave("-");
11909         gameInfo.white = StrSave(UserName());
11910         gameInfo.black = StrSave(first.tidy);
11911         gameInfo.timeControl = TimeControlTagValue();
11912         break;
11913
11914       case TwoMachinesPlay:
11915         gameInfo.event = StrSave( appData.pgnEventHeader );
11916         gameInfo.site = StrSave(HostName());
11917         gameInfo.date = PGNDate();
11918         if (matchGame > 0) {
11919             char buf[MSG_SIZ];
11920             sprintf(buf, "%d", matchGame);
11921             gameInfo.round = StrSave(buf);
11922         } else {
11923             gameInfo.round = StrSave("-");
11924         }
11925         if (first.twoMachinesColor[0] == 'w') {
11926             gameInfo.white = StrSave(first.tidy);
11927             gameInfo.black = StrSave(second.tidy);
11928         } else {
11929             gameInfo.white = StrSave(second.tidy);
11930             gameInfo.black = StrSave(first.tidy);
11931         }
11932         gameInfo.timeControl = TimeControlTagValue();
11933         break;
11934
11935       case EditGame:
11936         gameInfo.event = StrSave("Edited game");
11937         gameInfo.site = StrSave(HostName());
11938         gameInfo.date = PGNDate();
11939         gameInfo.round = StrSave("-");
11940         gameInfo.white = StrSave("-");
11941         gameInfo.black = StrSave("-");
11942         break;
11943
11944       case EditPosition:
11945         gameInfo.event = StrSave("Edited position");
11946         gameInfo.site = StrSave(HostName());
11947         gameInfo.date = PGNDate();
11948         gameInfo.round = StrSave("-");
11949         gameInfo.white = StrSave("-");
11950         gameInfo.black = StrSave("-");
11951         break;
11952
11953       case IcsPlayingWhite:
11954       case IcsPlayingBlack:
11955       case IcsObserving:
11956       case IcsExamining:
11957         break;
11958
11959       case PlayFromGameFile:
11960         gameInfo.event = StrSave("Game from non-PGN file");
11961         gameInfo.site = StrSave(HostName());
11962         gameInfo.date = PGNDate();
11963         gameInfo.round = StrSave("-");
11964         gameInfo.white = StrSave("?");
11965         gameInfo.black = StrSave("?");
11966         break;
11967
11968       default:
11969         break;
11970     }
11971 }
11972
11973 void
11974 ReplaceComment(index, text)
11975      int index;
11976      char *text;
11977 {
11978     int len;
11979
11980     while (*text == '\n') text++;
11981     len = strlen(text);
11982     while (len > 0 && text[len - 1] == '\n') len--;
11983
11984     if (commentList[index] != NULL)
11985       free(commentList[index]);
11986
11987     if (len == 0) {
11988         commentList[index] = NULL;
11989         return;
11990     }
11991     commentList[index] = (char *) malloc(len + 2);
11992     strncpy(commentList[index], text, len);
11993     commentList[index][len] = '\n';
11994     commentList[index][len + 1] = NULLCHAR;
11995 }
11996
11997 void
11998 CrushCRs(text)
11999      char *text;
12000 {
12001   char *p = text;
12002   char *q = text;
12003   char ch;
12004
12005   do {
12006     ch = *p++;
12007     if (ch == '\r') continue;
12008     *q++ = ch;
12009   } while (ch != '\0');
12010 }
12011
12012 void
12013 AppendComment(index, text)
12014      int index;
12015      char *text;
12016 {
12017     int oldlen, len;
12018     char *old;
12019
12020     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12021
12022     CrushCRs(text);
12023     while (*text == '\n') text++;
12024     len = strlen(text);
12025     while (len > 0 && text[len - 1] == '\n') len--;
12026
12027     if (len == 0) return;
12028
12029     if (commentList[index] != NULL) {
12030         old = commentList[index];
12031         oldlen = strlen(old);
12032         commentList[index] = (char *) malloc(oldlen + len + 2);
12033         strcpy(commentList[index], old);
12034         free(old);
12035         strncpy(&commentList[index][oldlen], text, len);
12036         commentList[index][oldlen + len] = '\n';
12037         commentList[index][oldlen + len + 1] = NULLCHAR;
12038     } else {
12039         commentList[index] = (char *) malloc(len + 2);
12040         strncpy(commentList[index], text, len);
12041         commentList[index][len] = '\n';
12042         commentList[index][len + 1] = NULLCHAR;
12043     }
12044 }
12045
12046 static char * FindStr( char * text, char * sub_text )
12047 {
12048     char * result = strstr( text, sub_text );
12049
12050     if( result != NULL ) {
12051         result += strlen( sub_text );
12052     }
12053
12054     return result;
12055 }
12056
12057 /* [AS] Try to extract PV info from PGN comment */
12058 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12059 char *GetInfoFromComment( int index, char * text )
12060 {
12061     char * sep = text;
12062
12063     if( text != NULL && index > 0 ) {
12064         int score = 0;
12065         int depth = 0;
12066         int time = -1, sec = 0, deci;
12067         char * s_eval = FindStr( text, "[%eval " );
12068         char * s_emt = FindStr( text, "[%emt " );
12069
12070         if( s_eval != NULL || s_emt != NULL ) {
12071             /* New style */
12072             char delim;
12073
12074             if( s_eval != NULL ) {
12075                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12076                     return text;
12077                 }
12078
12079                 if( delim != ']' ) {
12080                     return text;
12081                 }
12082             }
12083
12084             if( s_emt != NULL ) {
12085             }
12086         }
12087         else {
12088             /* We expect something like: [+|-]nnn.nn/dd */
12089             int score_lo = 0;
12090
12091             sep = strchr( text, '/' );
12092             if( sep == NULL || sep < (text+4) ) {
12093                 return text;
12094             }
12095
12096             time = -1; sec = -1; deci = -1;
12097             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12098                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12099                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12100                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12101                 return text;
12102             }
12103
12104             if( score_lo < 0 || score_lo >= 100 ) {
12105                 return text;
12106             }
12107
12108             if(sec >= 0) time = 600*time + 10*sec; else
12109             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12110
12111             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12112
12113             /* [HGM] PV time: now locate end of PV info */
12114             while( *++sep >= '0' && *sep <= '9'); // strip depth
12115             if(time >= 0)
12116             while( *++sep >= '0' && *sep <= '9'); // strip time
12117             if(sec >= 0)
12118             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12119             if(deci >= 0)
12120             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12121             while(*sep == ' ') sep++;
12122         }
12123
12124         if( depth <= 0 ) {
12125             return text;
12126         }
12127
12128         if( time < 0 ) {
12129             time = -1;
12130         }
12131
12132         pvInfoList[index-1].depth = depth;
12133         pvInfoList[index-1].score = score;
12134         pvInfoList[index-1].time  = 10*time; // centi-sec
12135     }
12136     return sep;
12137 }
12138
12139 void
12140 SendToProgram(message, cps)
12141      char *message;
12142      ChessProgramState *cps;
12143 {
12144     int count, outCount, error;
12145     char buf[MSG_SIZ];
12146
12147     if (cps->pr == NULL) return;
12148     Attention(cps);
12149
12150     if (appData.debugMode) {
12151         TimeMark now;
12152         GetTimeMark(&now);
12153         fprintf(debugFP, "%ld >%-6s: %s",
12154                 SubtractTimeMarks(&now, &programStartTime),
12155                 cps->which, message);
12156     }
12157
12158     count = strlen(message);
12159     outCount = OutputToProcess(cps->pr, message, count, &error);
12160     if (outCount < count && !exiting
12161                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12162         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12163         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12164             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12165                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12166                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12167             } else {
12168                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12169             }
12170             gameInfo.resultDetails = buf;
12171         }
12172         DisplayFatalError(buf, error, 1);
12173     }
12174 }
12175
12176 void
12177 ReceiveFromProgram(isr, closure, message, count, error)
12178      InputSourceRef isr;
12179      VOIDSTAR closure;
12180      char *message;
12181      int count;
12182      int error;
12183 {
12184     char *end_str;
12185     char buf[MSG_SIZ];
12186     ChessProgramState *cps = (ChessProgramState *)closure;
12187
12188     if (isr != cps->isr) return; /* Killed intentionally */
12189     if (count <= 0) {
12190         if (count == 0) {
12191             sprintf(buf,
12192                     _("Error: %s chess program (%s) exited unexpectedly"),
12193                     cps->which, cps->program);
12194         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12195                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12196                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12197                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12198                 } else {
12199                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12200                 }
12201                 gameInfo.resultDetails = buf;
12202             }
12203             RemoveInputSource(cps->isr);
12204             DisplayFatalError(buf, 0, 1);
12205         } else {
12206             sprintf(buf,
12207                     _("Error reading from %s chess program (%s)"),
12208                     cps->which, cps->program);
12209             RemoveInputSource(cps->isr);
12210
12211             /* [AS] Program is misbehaving badly... kill it */
12212             if( count == -2 ) {
12213                 DestroyChildProcess( cps->pr, 9 );
12214                 cps->pr = NoProc;
12215             }
12216
12217             DisplayFatalError(buf, error, 1);
12218         }
12219         return;
12220     }
12221
12222     if ((end_str = strchr(message, '\r')) != NULL)
12223       *end_str = NULLCHAR;
12224     if ((end_str = strchr(message, '\n')) != NULL)
12225       *end_str = NULLCHAR;
12226
12227     if (appData.debugMode) {
12228         TimeMark now; int print = 1;
12229         char *quote = ""; char c; int i;
12230
12231         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12232                 char start = message[0];
12233                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12234                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12235                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12236                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12237                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12238                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12239                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12240                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12241                         { quote = "# "; print = (appData.engineComments == 2); }
12242                 message[0] = start; // restore original message
12243         }
12244         if(print) {
12245                 GetTimeMark(&now);
12246                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12247                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12248                         quote,
12249                         message);
12250         }
12251     }
12252
12253     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12254     if (appData.icsEngineAnalyze) {
12255         if (strstr(message, "whisper") != NULL ||
12256              strstr(message, "kibitz") != NULL ||
12257             strstr(message, "tellics") != NULL) return;
12258     }
12259
12260     HandleMachineMove(message, cps);
12261 }
12262
12263
12264 void
12265 SendTimeControl(cps, mps, tc, inc, sd, st)
12266      ChessProgramState *cps;
12267      int mps, inc, sd, st;
12268      long tc;
12269 {
12270     char buf[MSG_SIZ];
12271     int seconds;
12272
12273     if( timeControl_2 > 0 ) {
12274         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12275             tc = timeControl_2;
12276         }
12277     }
12278     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12279     inc /= cps->timeOdds;
12280     st  /= cps->timeOdds;
12281
12282     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12283
12284     if (st > 0) {
12285       /* Set exact time per move, normally using st command */
12286       if (cps->stKludge) {
12287         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12288         seconds = st % 60;
12289         if (seconds == 0) {
12290           sprintf(buf, "level 1 %d\n", st/60);
12291         } else {
12292           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12293         }
12294       } else {
12295         sprintf(buf, "st %d\n", st);
12296       }
12297     } else {
12298       /* Set conventional or incremental time control, using level command */
12299       if (seconds == 0) {
12300         /* Note old gnuchess bug -- minutes:seconds used to not work.
12301            Fixed in later versions, but still avoid :seconds
12302            when seconds is 0. */
12303         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12304       } else {
12305         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12306                 seconds, inc/1000);
12307       }
12308     }
12309     SendToProgram(buf, cps);
12310
12311     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12312     /* Orthogonally, limit search to given depth */
12313     if (sd > 0) {
12314       if (cps->sdKludge) {
12315         sprintf(buf, "depth\n%d\n", sd);
12316       } else {
12317         sprintf(buf, "sd %d\n", sd);
12318       }
12319       SendToProgram(buf, cps);
12320     }
12321
12322     if(cps->nps > 0) { /* [HGM] nps */
12323         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12324         else {
12325                 sprintf(buf, "nps %d\n", cps->nps);
12326               SendToProgram(buf, cps);
12327         }
12328     }
12329 }
12330
12331 ChessProgramState *WhitePlayer()
12332 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12333 {
12334     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12335        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12336         return &second;
12337     return &first;
12338 }
12339
12340 void
12341 SendTimeRemaining(cps, machineWhite)
12342      ChessProgramState *cps;
12343      int /*boolean*/ machineWhite;
12344 {
12345     char message[MSG_SIZ];
12346     long time, otime;
12347
12348     /* Note: this routine must be called when the clocks are stopped
12349        or when they have *just* been set or switched; otherwise
12350        it will be off by the time since the current tick started.
12351     */
12352     if (machineWhite) {
12353         time = whiteTimeRemaining / 10;
12354         otime = blackTimeRemaining / 10;
12355     } else {
12356         time = blackTimeRemaining / 10;
12357         otime = whiteTimeRemaining / 10;
12358     }
12359     /* [HGM] translate opponent's time by time-odds factor */
12360     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12361     if (appData.debugMode) {
12362         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12363     }
12364
12365     if (time <= 0) time = 1;
12366     if (otime <= 0) otime = 1;
12367
12368     sprintf(message, "time %ld\n", time);
12369     SendToProgram(message, cps);
12370
12371     sprintf(message, "otim %ld\n", otime);
12372     SendToProgram(message, cps);
12373 }
12374
12375 int
12376 BoolFeature(p, name, loc, cps)
12377      char **p;
12378      char *name;
12379      int *loc;
12380      ChessProgramState *cps;
12381 {
12382   char buf[MSG_SIZ];
12383   int len = strlen(name);
12384   int val;
12385   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12386     (*p) += len + 1;
12387     sscanf(*p, "%d", &val);
12388     *loc = (val != 0);
12389     while (**p && **p != ' ') (*p)++;
12390     sprintf(buf, "accepted %s\n", name);
12391     SendToProgram(buf, cps);
12392     return TRUE;
12393   }
12394   return FALSE;
12395 }
12396
12397 int
12398 IntFeature(p, name, loc, cps)
12399      char **p;
12400      char *name;
12401      int *loc;
12402      ChessProgramState *cps;
12403 {
12404   char buf[MSG_SIZ];
12405   int len = strlen(name);
12406   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12407     (*p) += len + 1;
12408     sscanf(*p, "%d", loc);
12409     while (**p && **p != ' ') (*p)++;
12410     sprintf(buf, "accepted %s\n", name);
12411     SendToProgram(buf, cps);
12412     return TRUE;
12413   }
12414   return FALSE;
12415 }
12416
12417 int
12418 StringFeature(p, name, loc, cps)
12419      char **p;
12420      char *name;
12421      char loc[];
12422      ChessProgramState *cps;
12423 {
12424   char buf[MSG_SIZ];
12425   int len = strlen(name);
12426   if (strncmp((*p), name, len) == 0
12427       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12428     (*p) += len + 2;
12429     sscanf(*p, "%[^\"]", loc);
12430     while (**p && **p != '\"') (*p)++;
12431     if (**p == '\"') (*p)++;
12432     sprintf(buf, "accepted %s\n", name);
12433     SendToProgram(buf, cps);
12434     return TRUE;
12435   }
12436   return FALSE;
12437 }
12438
12439 int
12440 ParseOption(Option *opt, ChessProgramState *cps)
12441 // [HGM] options: process the string that defines an engine option, and determine
12442 // name, type, default value, and allowed value range
12443 {
12444         char *p, *q, buf[MSG_SIZ];
12445         int n, min = (-1)<<31, max = 1<<31, def;
12446
12447         if(p = strstr(opt->name, " -spin ")) {
12448             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12449             if(max < min) max = min; // enforce consistency
12450             if(def < min) def = min;
12451             if(def > max) def = max;
12452             opt->value = def;
12453             opt->min = min;
12454             opt->max = max;
12455             opt->type = Spin;
12456         } else if((p = strstr(opt->name, " -slider "))) {
12457             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12458             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12459             if(max < min) max = min; // enforce consistency
12460             if(def < min) def = min;
12461             if(def > max) def = max;
12462             opt->value = def;
12463             opt->min = min;
12464             opt->max = max;
12465             opt->type = Spin; // Slider;
12466         } else if((p = strstr(opt->name, " -string "))) {
12467             opt->textValue = p+9;
12468             opt->type = TextBox;
12469         } else if((p = strstr(opt->name, " -file "))) {
12470             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12471             opt->textValue = p+7;
12472             opt->type = TextBox; // FileName;
12473         } else if((p = strstr(opt->name, " -path "))) {
12474             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12475             opt->textValue = p+7;
12476             opt->type = TextBox; // PathName;
12477         } else if(p = strstr(opt->name, " -check ")) {
12478             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12479             opt->value = (def != 0);
12480             opt->type = CheckBox;
12481         } else if(p = strstr(opt->name, " -combo ")) {
12482             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12483             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12484             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12485             opt->value = n = 0;
12486             while(q = StrStr(q, " /// ")) {
12487                 n++; *q = 0;    // count choices, and null-terminate each of them
12488                 q += 5;
12489                 if(*q == '*') { // remember default, which is marked with * prefix
12490                     q++;
12491                     opt->value = n;
12492                 }
12493                 cps->comboList[cps->comboCnt++] = q;
12494             }
12495             cps->comboList[cps->comboCnt++] = NULL;
12496             opt->max = n + 1;
12497             opt->type = ComboBox;
12498         } else if(p = strstr(opt->name, " -button")) {
12499             opt->type = Button;
12500         } else if(p = strstr(opt->name, " -save")) {
12501             opt->type = SaveButton;
12502         } else return FALSE;
12503         *p = 0; // terminate option name
12504         // now look if the command-line options define a setting for this engine option.
12505         if(cps->optionSettings && cps->optionSettings[0])
12506             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12507         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12508                 sprintf(buf, "option %s", p);
12509                 if(p = strstr(buf, ",")) *p = 0;
12510                 strcat(buf, "\n");
12511                 SendToProgram(buf, cps);
12512         }
12513         return TRUE;
12514 }
12515
12516 void
12517 FeatureDone(cps, val)
12518      ChessProgramState* cps;
12519      int val;
12520 {
12521   DelayedEventCallback cb = GetDelayedEvent();
12522   if ((cb == InitBackEnd3 && cps == &first) ||
12523       (cb == TwoMachinesEventIfReady && cps == &second)) {
12524     CancelDelayedEvent();
12525     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12526   }
12527   cps->initDone = val;
12528 }
12529
12530 /* Parse feature command from engine */
12531 void
12532 ParseFeatures(args, cps)
12533      char* args;
12534      ChessProgramState *cps;
12535 {
12536   char *p = args;
12537   char *q;
12538   int val;
12539   char buf[MSG_SIZ];
12540
12541   for (;;) {
12542     while (*p == ' ') p++;
12543     if (*p == NULLCHAR) return;
12544
12545     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12546     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12547     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12548     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12549     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12550     if (BoolFeature(&p, "reuse", &val, cps)) {
12551       /* Engine can disable reuse, but can't enable it if user said no */
12552       if (!val) cps->reuse = FALSE;
12553       continue;
12554     }
12555     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12556     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12557       if (gameMode == TwoMachinesPlay) {
12558         DisplayTwoMachinesTitle();
12559       } else {
12560         DisplayTitle("");
12561       }
12562       continue;
12563     }
12564     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12565     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12566     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12567     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12568     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12569     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12570     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12571     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12572     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12573     if (IntFeature(&p, "done", &val, cps)) {
12574       FeatureDone(cps, val);
12575       continue;
12576     }
12577     /* Added by Tord: */
12578     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12579     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12580     /* End of additions by Tord */
12581
12582     /* [HGM] added features: */
12583     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12584     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12585     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12586     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12587     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12588     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12589     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12590         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12591             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12592             SendToProgram(buf, cps);
12593             continue;
12594         }
12595         if(cps->nrOptions >= MAX_OPTIONS) {
12596             cps->nrOptions--;
12597             sprintf(buf, "%s engine has too many options\n", cps->which);
12598             DisplayError(buf, 0);
12599         }
12600         continue;
12601     }
12602     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12603     /* End of additions by HGM */
12604
12605     /* unknown feature: complain and skip */
12606     q = p;
12607     while (*q && *q != '=') q++;
12608     sprintf(buf, "rejected %.*s\n", q-p, p);
12609     SendToProgram(buf, cps);
12610     p = q;
12611     if (*p == '=') {
12612       p++;
12613       if (*p == '\"') {
12614         p++;
12615         while (*p && *p != '\"') p++;
12616         if (*p == '\"') p++;
12617       } else {
12618         while (*p && *p != ' ') p++;
12619       }
12620     }
12621   }
12622
12623 }
12624
12625 void
12626 PeriodicUpdatesEvent(newState)
12627      int newState;
12628 {
12629     if (newState == appData.periodicUpdates)
12630       return;
12631
12632     appData.periodicUpdates=newState;
12633
12634     /* Display type changes, so update it now */
12635     DisplayAnalysis();
12636
12637     /* Get the ball rolling again... */
12638     if (newState) {
12639         AnalysisPeriodicEvent(1);
12640         StartAnalysisClock();
12641     }
12642 }
12643
12644 void
12645 PonderNextMoveEvent(newState)
12646      int newState;
12647 {
12648     if (newState == appData.ponderNextMove) return;
12649     if (gameMode == EditPosition) EditPositionDone();
12650     if (newState) {
12651         SendToProgram("hard\n", &first);
12652         if (gameMode == TwoMachinesPlay) {
12653             SendToProgram("hard\n", &second);
12654         }
12655     } else {
12656         SendToProgram("easy\n", &first);
12657         thinkOutput[0] = NULLCHAR;
12658         if (gameMode == TwoMachinesPlay) {
12659             SendToProgram("easy\n", &second);
12660         }
12661     }
12662     appData.ponderNextMove = newState;
12663 }
12664
12665 void
12666 NewSettingEvent(option, command, value)
12667      char *command;
12668      int option, value;
12669 {
12670     char buf[MSG_SIZ];
12671
12672     if (gameMode == EditPosition) EditPositionDone();
12673     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12674     SendToProgram(buf, &first);
12675     if (gameMode == TwoMachinesPlay) {
12676         SendToProgram(buf, &second);
12677     }
12678 }
12679
12680 void
12681 ShowThinkingEvent()
12682 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12683 {
12684     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12685     int newState = appData.showThinking
12686         // [HGM] thinking: other features now need thinking output as well
12687         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12688
12689     if (oldState == newState) return;
12690     oldState = newState;
12691     if (gameMode == EditPosition) EditPositionDone();
12692     if (oldState) {
12693         SendToProgram("post\n", &first);
12694         if (gameMode == TwoMachinesPlay) {
12695             SendToProgram("post\n", &second);
12696         }
12697     } else {
12698         SendToProgram("nopost\n", &first);
12699         thinkOutput[0] = NULLCHAR;
12700         if (gameMode == TwoMachinesPlay) {
12701             SendToProgram("nopost\n", &second);
12702         }
12703     }
12704 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12705 }
12706
12707 void
12708 AskQuestionEvent(title, question, replyPrefix, which)
12709      char *title; char *question; char *replyPrefix; char *which;
12710 {
12711   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12712   if (pr == NoProc) return;
12713   AskQuestion(title, question, replyPrefix, pr);
12714 }
12715
12716 void
12717 DisplayMove(moveNumber)
12718      int moveNumber;
12719 {
12720     char message[MSG_SIZ];
12721     char res[MSG_SIZ];
12722     char cpThinkOutput[MSG_SIZ];
12723
12724     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12725
12726     if (moveNumber == forwardMostMove - 1 ||
12727         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12728
12729         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12730
12731         if (strchr(cpThinkOutput, '\n')) {
12732             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12733         }
12734     } else {
12735         *cpThinkOutput = NULLCHAR;
12736     }
12737
12738     /* [AS] Hide thinking from human user */
12739     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12740         *cpThinkOutput = NULLCHAR;
12741         if( thinkOutput[0] != NULLCHAR ) {
12742             int i;
12743
12744             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12745                 cpThinkOutput[i] = '.';
12746             }
12747             cpThinkOutput[i] = NULLCHAR;
12748             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12749         }
12750     }
12751
12752     if (moveNumber == forwardMostMove - 1 &&
12753         gameInfo.resultDetails != NULL) {
12754         if (gameInfo.resultDetails[0] == NULLCHAR) {
12755             sprintf(res, " %s", PGNResult(gameInfo.result));
12756         } else {
12757             sprintf(res, " {%s} %s",
12758                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12759         }
12760     } else {
12761         res[0] = NULLCHAR;
12762     }
12763
12764     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12765         DisplayMessage(res, cpThinkOutput);
12766     } else {
12767         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12768                 WhiteOnMove(moveNumber) ? " " : ".. ",
12769                 parseList[moveNumber], res);
12770         DisplayMessage(message, cpThinkOutput);
12771     }
12772 }
12773
12774 void
12775 DisplayAnalysisText(text)
12776      char *text;
12777 {
12778   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12779       || appData.icsEngineAnalyze) 
12780     {
12781       EngineOutputPopUp();
12782     }
12783 }
12784
12785 static int
12786 only_one_move(str)
12787      char *str;
12788 {
12789     while (*str && isspace(*str)) ++str;
12790     while (*str && !isspace(*str)) ++str;
12791     if (!*str) return 1;
12792     while (*str && isspace(*str)) ++str;
12793     if (!*str) return 1;
12794     return 0;
12795 }
12796
12797 void
12798 DisplayAnalysis()
12799 {
12800     char buf[MSG_SIZ];
12801     char lst[MSG_SIZ / 2];
12802     double nps;
12803     static char *xtra[] = { "", " (--)", " (++)" };
12804     int h, m, s, cs;
12805
12806     if (programStats.time == 0) {
12807         programStats.time = 1;
12808     }
12809
12810     if (programStats.got_only_move) {
12811         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12812     } else {
12813         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12814
12815         nps = (u64ToDouble(programStats.nodes) /
12816              ((double)programStats.time /100.0));
12817
12818         cs = programStats.time % 100;
12819         s = programStats.time / 100;
12820         h = (s / (60*60));
12821         s = s - h*60*60;
12822         m = (s/60);
12823         s = s - m*60;
12824
12825         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12826           if (programStats.move_name[0] != NULLCHAR) {
12827             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12828                     programStats.depth,
12829                     programStats.nr_moves-programStats.moves_left,
12830                     programStats.nr_moves, programStats.move_name,
12831                     ((float)programStats.score)/100.0, lst,
12832                     only_one_move(lst)?
12833                     xtra[programStats.got_fail] : "",
12834                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12835           } else {
12836             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12837                     programStats.depth,
12838                     programStats.nr_moves-programStats.moves_left,
12839                     programStats.nr_moves, ((float)programStats.score)/100.0,
12840                     lst,
12841                     only_one_move(lst)?
12842                     xtra[programStats.got_fail] : "",
12843                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12844           }
12845         } else {
12846             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12847                     programStats.depth,
12848                     ((float)programStats.score)/100.0,
12849                     lst,
12850                     only_one_move(lst)?
12851                     xtra[programStats.got_fail] : "",
12852                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12853         }
12854     }
12855     DisplayAnalysisText(buf);
12856 }
12857
12858 void
12859 DisplayComment(moveNumber, text)
12860      int moveNumber;
12861      char *text;
12862 {
12863     char title[MSG_SIZ];
12864     char buf[8000]; // comment can be long!
12865     int score, depth;
12866
12867     if( appData.autoDisplayComment ) {
12868         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12869             strcpy(title, "Comment");
12870         } else {
12871             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12872                     WhiteOnMove(moveNumber) ? " " : ".. ",
12873                     parseList[moveNumber]);
12874         }
12875         // [HGM] PV info: display PV info together with (or as) comment
12876         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12877             if(text == NULL) text = "";
12878             score = pvInfoList[moveNumber].score;
12879             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12880                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12881             text = buf;
12882         }
12883     } else title[0] = 0;
12884
12885     if (text != NULL)
12886         CommentPopUp(title, text);
12887 }
12888
12889 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12890  * might be busy thinking or pondering.  It can be omitted if your
12891  * gnuchess is configured to stop thinking immediately on any user
12892  * input.  However, that gnuchess feature depends on the FIONREAD
12893  * ioctl, which does not work properly on some flavors of Unix.
12894  */
12895 void
12896 Attention(cps)
12897      ChessProgramState *cps;
12898 {
12899 #if ATTENTION
12900     if (!cps->useSigint) return;
12901     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12902     switch (gameMode) {
12903       case MachinePlaysWhite:
12904       case MachinePlaysBlack:
12905       case TwoMachinesPlay:
12906       case IcsPlayingWhite:
12907       case IcsPlayingBlack:
12908       case AnalyzeMode:
12909       case AnalyzeFile:
12910         /* Skip if we know it isn't thinking */
12911         if (!cps->maybeThinking) return;
12912         if (appData.debugMode)
12913           fprintf(debugFP, "Interrupting %s\n", cps->which);
12914         InterruptChildProcess(cps->pr);
12915         cps->maybeThinking = FALSE;
12916         break;
12917       default:
12918         break;
12919     }
12920 #endif /*ATTENTION*/
12921 }
12922
12923 int
12924 CheckFlags()
12925 {
12926     if (whiteTimeRemaining <= 0) {
12927         if (!whiteFlag) {
12928             whiteFlag = TRUE;
12929             if (appData.icsActive) {
12930                 if (appData.autoCallFlag &&
12931                     gameMode == IcsPlayingBlack && !blackFlag) {
12932                   SendToICS(ics_prefix);
12933                   SendToICS("flag\n");
12934                 }
12935             } else {
12936                 if (blackFlag) {
12937                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12938                 } else {
12939                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12940                     if (appData.autoCallFlag) {
12941                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12942                         return TRUE;
12943                     }
12944                 }
12945             }
12946         }
12947     }
12948     if (blackTimeRemaining <= 0) {
12949         if (!blackFlag) {
12950             blackFlag = TRUE;
12951             if (appData.icsActive) {
12952                 if (appData.autoCallFlag &&
12953                     gameMode == IcsPlayingWhite && !whiteFlag) {
12954                   SendToICS(ics_prefix);
12955                   SendToICS("flag\n");
12956                 }
12957             } else {
12958                 if (whiteFlag) {
12959                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12960                 } else {
12961                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12962                     if (appData.autoCallFlag) {
12963                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12964                         return TRUE;
12965                     }
12966                 }
12967             }
12968         }
12969     }
12970     return FALSE;
12971 }
12972
12973 void
12974 CheckTimeControl()
12975 {
12976     if (!appData.clockMode || appData.icsActive ||
12977         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12978
12979     /*
12980      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12981      */
12982     if ( !WhiteOnMove(forwardMostMove) )
12983         /* White made time control */
12984         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12985         /* [HGM] time odds: correct new time quota for time odds! */
12986                                             / WhitePlayer()->timeOdds;
12987       else
12988         /* Black made time control */
12989         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12990                                             / WhitePlayer()->other->timeOdds;
12991 }
12992
12993 void
12994 DisplayBothClocks()
12995 {
12996     int wom = gameMode == EditPosition ?
12997       !blackPlaysFirst : WhiteOnMove(currentMove);
12998     DisplayWhiteClock(whiteTimeRemaining, wom);
12999     DisplayBlackClock(blackTimeRemaining, !wom);
13000 }
13001
13002
13003 /* Timekeeping seems to be a portability nightmare.  I think everyone
13004    has ftime(), but I'm really not sure, so I'm including some ifdefs
13005    to use other calls if you don't.  Clocks will be less accurate if
13006    you have neither ftime nor gettimeofday.
13007 */
13008
13009 /* VS 2008 requires the #include outside of the function */
13010 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13011 #include <sys/timeb.h>
13012 #endif
13013
13014 /* Get the current time as a TimeMark */
13015 void
13016 GetTimeMark(tm)
13017      TimeMark *tm;
13018 {
13019 #if HAVE_GETTIMEOFDAY
13020
13021     struct timeval timeVal;
13022     struct timezone timeZone;
13023
13024     gettimeofday(&timeVal, &timeZone);
13025     tm->sec = (long) timeVal.tv_sec;
13026     tm->ms = (int) (timeVal.tv_usec / 1000L);
13027
13028 #else /*!HAVE_GETTIMEOFDAY*/
13029 #if HAVE_FTIME
13030
13031 // include <sys/timeb.h> / moved to just above start of function
13032     struct timeb timeB;
13033
13034     ftime(&timeB);
13035     tm->sec = (long) timeB.time;
13036     tm->ms = (int) timeB.millitm;
13037
13038 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13039     tm->sec = (long) time(NULL);
13040     tm->ms = 0;
13041 #endif
13042 #endif
13043 }
13044
13045 /* Return the difference in milliseconds between two
13046    time marks.  We assume the difference will fit in a long!
13047 */
13048 long
13049 SubtractTimeMarks(tm2, tm1)
13050      TimeMark *tm2, *tm1;
13051 {
13052     return 1000L*(tm2->sec - tm1->sec) +
13053            (long) (tm2->ms - tm1->ms);
13054 }
13055
13056
13057 /*
13058  * Code to manage the game clocks.
13059  *
13060  * In tournament play, black starts the clock and then white makes a move.
13061  * We give the human user a slight advantage if he is playing white---the
13062  * clocks don't run until he makes his first move, so it takes zero time.
13063  * Also, we don't account for network lag, so we could get out of sync
13064  * with GNU Chess's clock -- but then, referees are always right.
13065  */
13066
13067 static TimeMark tickStartTM;
13068 static long intendedTickLength;
13069
13070 long
13071 NextTickLength(timeRemaining)
13072      long timeRemaining;
13073 {
13074     long nominalTickLength, nextTickLength;
13075
13076     if (timeRemaining > 0L && timeRemaining <= 10000L)
13077       nominalTickLength = 100L;
13078     else
13079       nominalTickLength = 1000L;
13080     nextTickLength = timeRemaining % nominalTickLength;
13081     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13082
13083     return nextTickLength;
13084 }
13085
13086 /* Adjust clock one minute up or down */
13087 void
13088 AdjustClock(Boolean which, int dir)
13089 {
13090     if(which) blackTimeRemaining += 60000*dir;
13091     else      whiteTimeRemaining += 60000*dir;
13092     DisplayBothClocks();
13093 }
13094
13095 /* Stop clocks and reset to a fresh time control */
13096 void
13097 ResetClocks()
13098 {
13099     (void) StopClockTimer();
13100     if (appData.icsActive) {
13101         whiteTimeRemaining = blackTimeRemaining = 0;
13102     } else { /* [HGM] correct new time quote for time odds */
13103         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13104         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13105     }
13106     if (whiteFlag || blackFlag) {
13107         DisplayTitle("");
13108         whiteFlag = blackFlag = FALSE;
13109     }
13110     DisplayBothClocks();
13111 }
13112
13113 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13114
13115 /* Decrement running clock by amount of time that has passed */
13116 void
13117 DecrementClocks()
13118 {
13119     long timeRemaining;
13120     long lastTickLength, fudge;
13121     TimeMark now;
13122
13123     if (!appData.clockMode) return;
13124     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13125
13126     GetTimeMark(&now);
13127
13128     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13129
13130     /* Fudge if we woke up a little too soon */
13131     fudge = intendedTickLength - lastTickLength;
13132     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13133
13134     if (WhiteOnMove(forwardMostMove)) {
13135         if(whiteNPS >= 0) lastTickLength = 0;
13136         timeRemaining = whiteTimeRemaining -= lastTickLength;
13137         DisplayWhiteClock(whiteTimeRemaining - fudge,
13138                           WhiteOnMove(currentMove));
13139     } else {
13140         if(blackNPS >= 0) lastTickLength = 0;
13141         timeRemaining = blackTimeRemaining -= lastTickLength;
13142         DisplayBlackClock(blackTimeRemaining - fudge,
13143                           !WhiteOnMove(currentMove));
13144     }
13145
13146     if (CheckFlags()) return;
13147
13148     tickStartTM = now;
13149     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13150     StartClockTimer(intendedTickLength);
13151
13152     /* if the time remaining has fallen below the alarm threshold, sound the
13153      * alarm. if the alarm has sounded and (due to a takeback or time control
13154      * with increment) the time remaining has increased to a level above the
13155      * threshold, reset the alarm so it can sound again.
13156      */
13157
13158     if (appData.icsActive && appData.icsAlarm) {
13159
13160         /* make sure we are dealing with the user's clock */
13161         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13162                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13163            )) return;
13164
13165         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13166             alarmSounded = FALSE;
13167         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13168             PlayAlarmSound();
13169             alarmSounded = TRUE;
13170         }
13171     }
13172 }
13173
13174
13175 /* A player has just moved, so stop the previously running
13176    clock and (if in clock mode) start the other one.
13177    We redisplay both clocks in case we're in ICS mode, because
13178    ICS gives us an update to both clocks after every move.
13179    Note that this routine is called *after* forwardMostMove
13180    is updated, so the last fractional tick must be subtracted
13181    from the color that is *not* on move now.
13182 */
13183 void
13184 SwitchClocks()
13185 {
13186     long lastTickLength;
13187     TimeMark now;
13188     int flagged = FALSE;
13189
13190     GetTimeMark(&now);
13191
13192     if (StopClockTimer() && appData.clockMode) {
13193         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13194         if (WhiteOnMove(forwardMostMove)) {
13195             if(blackNPS >= 0) lastTickLength = 0;
13196             blackTimeRemaining -= lastTickLength;
13197            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13198 //         if(pvInfoList[forwardMostMove-1].time == -1)
13199                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13200                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13201         } else {
13202            if(whiteNPS >= 0) lastTickLength = 0;
13203            whiteTimeRemaining -= lastTickLength;
13204            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13205 //         if(pvInfoList[forwardMostMove-1].time == -1)
13206                  pvInfoList[forwardMostMove-1].time =
13207                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13208         }
13209         flagged = CheckFlags();
13210     }
13211     CheckTimeControl();
13212
13213     if (flagged || !appData.clockMode) return;
13214
13215     switch (gameMode) {
13216       case MachinePlaysBlack:
13217       case MachinePlaysWhite:
13218       case BeginningOfGame:
13219         if (pausing) return;
13220         break;
13221
13222       case EditGame:
13223       case PlayFromGameFile:
13224       case IcsExamining:
13225         return;
13226
13227       default:
13228         break;
13229     }
13230
13231     tickStartTM = now;
13232     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13233       whiteTimeRemaining : blackTimeRemaining);
13234     StartClockTimer(intendedTickLength);
13235 }
13236
13237
13238 /* Stop both clocks */
13239 void
13240 StopClocks()
13241 {
13242     long lastTickLength;
13243     TimeMark now;
13244
13245     if (!StopClockTimer()) return;
13246     if (!appData.clockMode) return;
13247
13248     GetTimeMark(&now);
13249
13250     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13251     if (WhiteOnMove(forwardMostMove)) {
13252         if(whiteNPS >= 0) lastTickLength = 0;
13253         whiteTimeRemaining -= lastTickLength;
13254         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13255     } else {
13256         if(blackNPS >= 0) lastTickLength = 0;
13257         blackTimeRemaining -= lastTickLength;
13258         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13259     }
13260     CheckFlags();
13261 }
13262
13263 /* Start clock of player on move.  Time may have been reset, so
13264    if clock is already running, stop and restart it. */
13265 void
13266 StartClocks()
13267 {
13268     (void) StopClockTimer(); /* in case it was running already */
13269     DisplayBothClocks();
13270     if (CheckFlags()) return;
13271
13272     if (!appData.clockMode) return;
13273     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13274
13275     GetTimeMark(&tickStartTM);
13276     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13277       whiteTimeRemaining : blackTimeRemaining);
13278
13279    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13280     whiteNPS = blackNPS = -1;
13281     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13282        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13283         whiteNPS = first.nps;
13284     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13285        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13286         blackNPS = first.nps;
13287     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13288         whiteNPS = second.nps;
13289     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13290         blackNPS = second.nps;
13291     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13292
13293     StartClockTimer(intendedTickLength);
13294 }
13295
13296 char *
13297 TimeString(ms)
13298      long ms;
13299 {
13300     long second, minute, hour, day;
13301     char *sign = "";
13302     static char buf[32];
13303
13304     if (ms > 0 && ms <= 9900) {
13305       /* convert milliseconds to tenths, rounding up */
13306       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13307
13308       sprintf(buf, " %03.1f ", tenths/10.0);
13309       return buf;
13310     }
13311
13312     /* convert milliseconds to seconds, rounding up */
13313     /* use floating point to avoid strangeness of integer division
13314        with negative dividends on many machines */
13315     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13316
13317     if (second < 0) {
13318         sign = "-";
13319         second = -second;
13320     }
13321
13322     day = second / (60 * 60 * 24);
13323     second = second % (60 * 60 * 24);
13324     hour = second / (60 * 60);
13325     second = second % (60 * 60);
13326     minute = second / 60;
13327     second = second % 60;
13328
13329     if (day > 0)
13330       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13331               sign, day, hour, minute, second);
13332     else if (hour > 0)
13333       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13334     else
13335       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13336
13337     return buf;
13338 }
13339
13340
13341 /*
13342  * This is necessary because some C libraries aren't ANSI C compliant yet.
13343  */
13344 char *
13345 StrStr(string, match)
13346      char *string, *match;
13347 {
13348     int i, length;
13349
13350     length = strlen(match);
13351
13352     for (i = strlen(string) - length; i >= 0; i--, string++)
13353       if (!strncmp(match, string, length))
13354         return string;
13355
13356     return NULL;
13357 }
13358
13359 char *
13360 StrCaseStr(string, match)
13361      char *string, *match;
13362 {
13363     int i, j, length;
13364
13365     length = strlen(match);
13366
13367     for (i = strlen(string) - length; i >= 0; i--, string++) {
13368         for (j = 0; j < length; j++) {
13369             if (ToLower(match[j]) != ToLower(string[j]))
13370               break;
13371         }
13372         if (j == length) return string;
13373     }
13374
13375     return NULL;
13376 }
13377
13378 #ifndef _amigados
13379 int
13380 StrCaseCmp(s1, s2)
13381      char *s1, *s2;
13382 {
13383     char c1, c2;
13384
13385     for (;;) {
13386         c1 = ToLower(*s1++);
13387         c2 = ToLower(*s2++);
13388         if (c1 > c2) return 1;
13389         if (c1 < c2) return -1;
13390         if (c1 == NULLCHAR) return 0;
13391     }
13392 }
13393
13394
13395 int
13396 ToLower(c)
13397      int c;
13398 {
13399     return isupper(c) ? tolower(c) : c;
13400 }
13401
13402
13403 int
13404 ToUpper(c)
13405      int c;
13406 {
13407     return islower(c) ? toupper(c) : c;
13408 }
13409 #endif /* !_amigados    */
13410
13411 char *
13412 StrSave(s)
13413      char *s;
13414 {
13415     char *ret;
13416
13417     if ((ret = (char *) malloc(strlen(s) + 1))) {
13418         strcpy(ret, s);
13419     }
13420     return ret;
13421 }
13422
13423 char *
13424 StrSavePtr(s, savePtr)
13425      char *s, **savePtr;
13426 {
13427     if (*savePtr) {
13428         free(*savePtr);
13429     }
13430     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13431         strcpy(*savePtr, s);
13432     }
13433     return(*savePtr);
13434 }
13435
13436 char *
13437 PGNDate()
13438 {
13439     time_t clock;
13440     struct tm *tm;
13441     char buf[MSG_SIZ];
13442
13443     clock = time((time_t *)NULL);
13444     tm = localtime(&clock);
13445     sprintf(buf, "%04d.%02d.%02d",
13446             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13447     return StrSave(buf);
13448 }
13449
13450
13451 char *
13452 PositionToFEN(move, overrideCastling)
13453      int move;
13454      char *overrideCastling;
13455 {
13456     int i, j, fromX, fromY, toX, toY;
13457     int whiteToPlay;
13458     char buf[128];
13459     char *p, *q;
13460     int emptycount;
13461     ChessSquare piece;
13462
13463     whiteToPlay = (gameMode == EditPosition) ?
13464       !blackPlaysFirst : (move % 2 == 0);
13465     p = buf;
13466
13467     /* Piece placement data */
13468     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13469         emptycount = 0;
13470         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13471             if (boards[move][i][j] == EmptySquare) {
13472                 emptycount++;
13473             } else { ChessSquare piece = boards[move][i][j];
13474                 if (emptycount > 0) {
13475                     if(emptycount<10) /* [HGM] can be >= 10 */
13476                         *p++ = '0' + emptycount;
13477                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13478                     emptycount = 0;
13479                 }
13480                 if(PieceToChar(piece) == '+') {
13481                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13482                     *p++ = '+';
13483                     piece = (ChessSquare)(DEMOTED piece);
13484                 }
13485                 *p++ = PieceToChar(piece);
13486                 if(p[-1] == '~') {
13487                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13488                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13489                     *p++ = '~';
13490                 }
13491             }
13492         }
13493         if (emptycount > 0) {
13494             if(emptycount<10) /* [HGM] can be >= 10 */
13495                 *p++ = '0' + emptycount;
13496             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13497             emptycount = 0;
13498         }
13499         *p++ = '/';
13500     }
13501     *(p - 1) = ' ';
13502
13503     /* [HGM] print Crazyhouse or Shogi holdings */
13504     if( gameInfo.holdingsWidth ) {
13505         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13506         q = p;
13507         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13508             piece = boards[move][i][BOARD_WIDTH-1];
13509             if( piece != EmptySquare )
13510               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13511                   *p++ = PieceToChar(piece);
13512         }
13513         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13514             piece = boards[move][BOARD_HEIGHT-i-1][0];
13515             if( piece != EmptySquare )
13516               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13517                   *p++ = PieceToChar(piece);
13518         }
13519
13520         if( q == p ) *p++ = '-';
13521         *p++ = ']';
13522         *p++ = ' ';
13523     }
13524
13525     /* Active color */
13526     *p++ = whiteToPlay ? 'w' : 'b';
13527     *p++ = ' ';
13528
13529   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13530     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13531   } else {
13532   if(nrCastlingRights) {
13533      q = p;
13534      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13535        /* [HGM] write directly from rights */
13536            if(castlingRights[move][2] >= 0 &&
13537               castlingRights[move][0] >= 0   )
13538                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13539            if(castlingRights[move][2] >= 0 &&
13540               castlingRights[move][1] >= 0   )
13541                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13542            if(castlingRights[move][5] >= 0 &&
13543               castlingRights[move][3] >= 0   )
13544                 *p++ = castlingRights[move][3] + AAA;
13545            if(castlingRights[move][5] >= 0 &&
13546               castlingRights[move][4] >= 0   )
13547                 *p++ = castlingRights[move][4] + AAA;
13548      } else {
13549
13550         /* [HGM] write true castling rights */
13551         if( nrCastlingRights == 6 ) {
13552             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13553                castlingRights[move][2] >= 0  ) *p++ = 'K';
13554             if(castlingRights[move][1] == BOARD_LEFT &&
13555                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13556             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13557                castlingRights[move][5] >= 0  ) *p++ = 'k';
13558             if(castlingRights[move][4] == BOARD_LEFT &&
13559                castlingRights[move][5] >= 0  ) *p++ = 'q';
13560         }
13561      }
13562      if (q == p) *p++ = '-'; /* No castling rights */
13563      *p++ = ' ';
13564   }
13565
13566   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13567      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13568     /* En passant target square */
13569     if (move > backwardMostMove) {
13570         fromX = moveList[move - 1][0] - AAA;
13571         fromY = moveList[move - 1][1] - ONE;
13572         toX = moveList[move - 1][2] - AAA;
13573         toY = moveList[move - 1][3] - ONE;
13574         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13575             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13576             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13577             fromX == toX) {
13578             /* 2-square pawn move just happened */
13579             *p++ = toX + AAA;
13580             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13581         } else {
13582             *p++ = '-';
13583         }
13584     } else if(move == backwardMostMove) {
13585         // [HGM] perhaps we should always do it like this, and forget the above?
13586         if(epStatus[move] >= 0) {
13587             *p++ = epStatus[move] + AAA;
13588             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13589         } else {
13590             *p++ = '-';
13591         }
13592     } else {
13593         *p++ = '-';
13594     }
13595     *p++ = ' ';
13596   }
13597   }
13598
13599     /* [HGM] find reversible plies */
13600     {   int i = 0, j=move;
13601
13602         if (appData.debugMode) { int k;
13603             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13604             for(k=backwardMostMove; k<=forwardMostMove; k++)
13605                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13606
13607         }
13608
13609         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13610         if( j == backwardMostMove ) i += initialRulePlies;
13611         sprintf(p, "%d ", i);
13612         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13613     }
13614     /* Fullmove number */
13615     sprintf(p, "%d", (move / 2) + 1);
13616
13617     return StrSave(buf);
13618 }
13619
13620 Boolean
13621 ParseFEN(board, blackPlaysFirst, fen)
13622     Board board;
13623      int *blackPlaysFirst;
13624      char *fen;
13625 {
13626     int i, j;
13627     char *p;
13628     int emptycount;
13629     ChessSquare piece;
13630
13631     p = fen;
13632
13633     /* [HGM] by default clear Crazyhouse holdings, if present */
13634     if(gameInfo.holdingsWidth) {
13635        for(i=0; i<BOARD_HEIGHT; i++) {
13636            board[i][0]             = EmptySquare; /* black holdings */
13637            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13638            board[i][1]             = (ChessSquare) 0; /* black counts */
13639            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13640        }
13641     }
13642
13643     /* Piece placement data */
13644     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13645         j = 0;
13646         for (;;) {
13647             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13648                 if (*p == '/') p++;
13649                 emptycount = gameInfo.boardWidth - j;
13650                 while (emptycount--)
13651                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13652                 break;
13653 #if(BOARD_SIZE >= 10)
13654             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13655                 p++; emptycount=10;
13656                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13657                 while (emptycount--)
13658                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13659 #endif
13660             } else if (isdigit(*p)) {
13661                 emptycount = *p++ - '0';
13662                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13663                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13664                 while (emptycount--)
13665                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13666             } else if (*p == '+' || isalpha(*p)) {
13667                 if (j >= gameInfo.boardWidth) return FALSE;
13668                 if(*p=='+') {
13669                     piece = CharToPiece(*++p);
13670                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13671                     piece = (ChessSquare) (PROMOTED piece ); p++;
13672                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13673                 } else piece = CharToPiece(*p++);
13674
13675                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13676                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13677                     piece = (ChessSquare) (PROMOTED piece);
13678                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13679                     p++;
13680                 }
13681                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13682             } else {
13683                 return FALSE;
13684             }
13685         }
13686     }
13687     while (*p == '/' || *p == ' ') p++;
13688
13689     /* [HGM] look for Crazyhouse holdings here */
13690     while(*p==' ') p++;
13691     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13692         if(*p == '[') p++;
13693         if(*p == '-' ) *p++; /* empty holdings */ else {
13694             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13695             /* if we would allow FEN reading to set board size, we would   */
13696             /* have to add holdings and shift the board read so far here   */
13697             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13698                 *p++;
13699                 if((int) piece >= (int) BlackPawn ) {
13700                     i = (int)piece - (int)BlackPawn;
13701                     i = PieceToNumber((ChessSquare)i);
13702                     if( i >= gameInfo.holdingsSize ) return FALSE;
13703                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13704                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13705                 } else {
13706                     i = (int)piece - (int)WhitePawn;
13707                     i = PieceToNumber((ChessSquare)i);
13708                     if( i >= gameInfo.holdingsSize ) return FALSE;
13709                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13710                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13711                 }
13712             }
13713         }
13714         if(*p == ']') *p++;
13715     }
13716
13717     while(*p == ' ') p++;
13718
13719     /* Active color */
13720     switch (*p++) {
13721       case 'w':
13722         *blackPlaysFirst = FALSE;
13723         break;
13724       case 'b':
13725         *blackPlaysFirst = TRUE;
13726         break;
13727       default:
13728         return FALSE;
13729     }
13730
13731     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13732     /* return the extra info in global variiables             */
13733
13734     /* set defaults in case FEN is incomplete */
13735     FENepStatus = EP_UNKNOWN;
13736     for(i=0; i<nrCastlingRights; i++ ) {
13737         FENcastlingRights[i] =
13738             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13739     }   /* assume possible unless obviously impossible */
13740     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13741     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13742     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13743     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13744     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13745     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13746     FENrulePlies = 0;
13747
13748     while(*p==' ') p++;
13749     if(nrCastlingRights) {
13750       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13751           /* castling indicator present, so default becomes no castlings */
13752           for(i=0; i<nrCastlingRights; i++ ) {
13753                  FENcastlingRights[i] = -1;
13754           }
13755       }
13756       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13757              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13758              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13759              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13760         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13761
13762         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13763             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13764             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13765         }
13766         switch(c) {
13767           case'K':
13768               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13769               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13770               FENcastlingRights[2] = whiteKingFile;
13771               break;
13772           case'Q':
13773               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13774               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13775               FENcastlingRights[2] = whiteKingFile;
13776               break;
13777           case'k':
13778               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13779               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13780               FENcastlingRights[5] = blackKingFile;
13781               break;
13782           case'q':
13783               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13784               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13785               FENcastlingRights[5] = blackKingFile;
13786           case '-':
13787               break;
13788           default: /* FRC castlings */
13789               if(c >= 'a') { /* black rights */
13790                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13791                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13792                   if(i == BOARD_RGHT) break;
13793                   FENcastlingRights[5] = i;
13794                   c -= AAA;
13795                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13796                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13797                   if(c > i)
13798                       FENcastlingRights[3] = c;
13799                   else
13800                       FENcastlingRights[4] = c;
13801               } else { /* white rights */
13802                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13803                     if(board[0][i] == WhiteKing) break;
13804                   if(i == BOARD_RGHT) break;
13805                   FENcastlingRights[2] = i;
13806                   c -= AAA - 'a' + 'A';
13807                   if(board[0][c] >= WhiteKing) break;
13808                   if(c > i)
13809                       FENcastlingRights[0] = c;
13810                   else
13811                       FENcastlingRights[1] = c;
13812               }
13813         }
13814       }
13815     if (appData.debugMode) {
13816         fprintf(debugFP, "FEN castling rights:");
13817         for(i=0; i<nrCastlingRights; i++)
13818         fprintf(debugFP, " %d", FENcastlingRights[i]);
13819         fprintf(debugFP, "\n");
13820     }
13821
13822       while(*p==' ') p++;
13823     }
13824
13825     /* read e.p. field in games that know e.p. capture */
13826     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13827        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13828       if(*p=='-') {
13829         p++; FENepStatus = EP_NONE;
13830       } else {
13831          char c = *p++ - AAA;
13832
13833          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13834          if(*p >= '0' && *p <='9') *p++;
13835          FENepStatus = c;
13836       }
13837     }
13838
13839
13840     if(sscanf(p, "%d", &i) == 1) {
13841         FENrulePlies = i; /* 50-move ply counter */
13842         /* (The move number is still ignored)    */
13843     }
13844
13845     return TRUE;
13846 }
13847
13848 void
13849 EditPositionPasteFEN(char *fen)
13850 {
13851   if (fen != NULL) {
13852     Board initial_position;
13853
13854     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13855       DisplayError(_("Bad FEN position in clipboard"), 0);
13856       return ;
13857     } else {
13858       int savedBlackPlaysFirst = blackPlaysFirst;
13859       EditPositionEvent();
13860       blackPlaysFirst = savedBlackPlaysFirst;
13861       CopyBoard(boards[0], initial_position);
13862           /* [HGM] copy FEN attributes as well */
13863           {   int i;
13864               initialRulePlies = FENrulePlies;
13865               epStatus[0] = FENepStatus;
13866               for( i=0; i<nrCastlingRights; i++ )
13867                   castlingRights[0][i] = FENcastlingRights[i];
13868           }
13869       EditPositionDone();
13870       DisplayBothClocks();
13871       DrawPosition(FALSE, boards[currentMove]);
13872     }
13873   }
13874 }