Merge commit 'v4.4.1.20091022' 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
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom:
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /*
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0;
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000;
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041   if (appData.debugMode) {
1042     fprintf(debugFP, "%s\n", programVersion);
1043   }
1044
1045   set_cont_sequence(appData.wrapContSeq);
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     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908         return; // prevent overwriting by pre-board holdings
1909
1910     if( (int)lowestPiece >= BlackPawn ) {
1911         holdingsColumn = 0;
1912         countsColumn = 1;
1913         holdingsStartRow = BOARD_HEIGHT-1;
1914         direction = -1;
1915     } else {
1916         holdingsColumn = BOARD_WIDTH-1;
1917         countsColumn = BOARD_WIDTH-2;
1918         holdingsStartRow = 0;
1919         direction = 1;
1920     }
1921
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923         board[i][holdingsColumn] = EmptySquare;
1924         board[i][countsColumn]   = (ChessSquare) 0;
1925     }
1926     while( (p=*holdings++) != NULLCHAR ) {
1927         piece = CharToPiece( ToUpper(p) );
1928         if(piece == EmptySquare) continue;
1929         /*j = (int) piece - (int) WhitePawn;*/
1930         j = PieceToNumber(piece);
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932         if(j < 0) continue;               /* should not happen */
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935         board[holdingsStartRow+j*direction][countsColumn]++;
1936     }
1937 }
1938
1939
1940 void
1941 VariantSwitch(Board board, VariantClass newVariant)
1942 {
1943    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944
1945    startedFromPositionFile = FALSE;
1946    if(gameInfo.variant == newVariant) return;
1947
1948    /* [HGM] This routine is called each time an assignment is made to
1949     * gameInfo.variant during a game, to make sure the board sizes
1950     * are set to match the new variant. If that means adding or deleting
1951     * holdings, we shift the playing board accordingly
1952     * This kludge is needed because in ICS observe mode, we get boards
1953     * of an ongoing game without knowing the variant, and learn about the
1954     * latter only later. This can be because of the move list we requested,
1955     * in which case the game history is refilled from the beginning anyway,
1956     * but also when receiving holdings of a crazyhouse game. In the latter
1957     * case we want to add those holdings to the already received position.
1958     */
1959    
1960    if (appData.debugMode) {
1961      fprintf(debugFP, "Switch board from %s to %s\n",
1962              VariantName(gameInfo.variant), VariantName(newVariant));
1963      setbuf(debugFP, NULL);
1964    }
1965    shuffleOpenings = 0;       /* [HGM] shuffle */
1966    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1967    switch(newVariant) 
1968      {
1969      case VariantShogi:
1970        newWidth = 9;  newHeight = 9;
1971        gameInfo.holdingsSize = 7;
1972      case VariantBughouse:
1973      case VariantCrazyhouse:
1974        newHoldingsWidth = 2; break;
1975      case VariantGreat:
1976        newWidth = 10;
1977      case VariantSuper:
1978        newHoldingsWidth = 2;
1979        gameInfo.holdingsSize = 8;
1980        break;
1981      case VariantGothic:
1982      case VariantCapablanca:
1983      case VariantCapaRandom:
1984        newWidth = 10;
1985      default:
1986        newHoldingsWidth = gameInfo.holdingsSize = 0;
1987      };
1988    
1989    if(newWidth  != gameInfo.boardWidth  ||
1990       newHeight != gameInfo.boardHeight ||
1991       newHoldingsWidth != gameInfo.holdingsWidth ) {
1992      
1993      /* shift position to new playing area, if needed */
1994      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995        for(i=0; i<BOARD_HEIGHT; i++) 
1996          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998              board[i][j];
1999        for(i=0; i<newHeight; i++) {
2000          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002        }
2003      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004        for(i=0; i<BOARD_HEIGHT; i++)
2005          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2007              board[i][j];
2008      }
2009      gameInfo.boardWidth  = newWidth;
2010      gameInfo.boardHeight = newHeight;
2011      gameInfo.holdingsWidth = newHoldingsWidth;
2012      gameInfo.variant = newVariant;
2013      InitDrawingSizes(-2, 0);
2014      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2015    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
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 char cont_seq[] = "\n\\   ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     static int cmatch = 0; // continuation sequence match
2060     char *bp;
2061     char str[500];
2062     int i, oldi;
2063     int buf_len;
2064     int next_out;
2065     int tkind;
2066     int backup;    /* [DM] For zippy color lines */
2067     char *p;
2068     char talker[MSG_SIZ]; // [HGM] chat
2069     int channel;
2070
2071     if (appData.debugMode) {
2072       if (!error) {
2073         fprintf(debugFP, "<ICS: ");
2074         show_bytes(debugFP, data, count);
2075         fprintf(debugFP, "\n");
2076       }
2077     }
2078
2079     if (appData.debugMode) { int f = forwardMostMove;
2080         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2082     }
2083     if (count > 0) {
2084         /* If last read ended with a partial line that we couldn't parse,
2085            prepend it to the new read and try again. */
2086         if (leftover_len > 0) {
2087             for (i=0; i<leftover_len; i++)
2088               buf[i] = buf[leftover_start + i];
2089         }
2090
2091     /* copy new characters into the buffer */
2092     bp = buf + leftover_len;
2093     buf_len=leftover_len;
2094     for (i=0; i<count; i++)
2095     {
2096         // ignore these
2097         if (data[i] == '\r')
2098             continue;
2099
2100         // join lines split by ICS?
2101         if (!appData.noJoin)
2102         {
2103             /*
2104                 Joining just consists of finding matches against the
2105                 continuation sequence, and discarding that sequence
2106                 if found instead of copying it.  So, until a match
2107                 fails, there's nothing to do since it might be the
2108                 complete sequence, and thus, something we don't want
2109                 copied.
2110             */
2111             if (data[i] == cont_seq[cmatch])
2112             {
2113                 cmatch++;
2114                 if (cmatch == strlen(cont_seq))
2115                 {
2116                     cmatch = 0; // complete match.  just reset the counter
2117
2118                     /*
2119                         it's possible for the ICS to not include the space
2120                         at the end of the last word, making our [correct]
2121                         join operation fuse two separate words.  the server
2122                         does this when the space occurs at the width setting.
2123                     */
2124                     if (!buf_len || buf[buf_len-1] != ' ')
2125                     {
2126                         *bp++ = ' ';
2127                         buf_len++;
2128                     }
2129                 }
2130                 continue;
2131             }
2132             else if (cmatch)
2133             {
2134                 /*
2135                     match failed, so we have to copy what matched before
2136                     falling through and copying this character.  In reality,
2137                     this will only ever be just the newline character, but
2138                     it doesn't hurt to be precise.
2139                 */
2140                 strncpy(bp, cont_seq, cmatch);
2141                 bp += cmatch;
2142                 buf_len += cmatch;
2143                 cmatch = 0;
2144             }
2145         }
2146
2147         // copy this char
2148         *bp++ = data[i];
2149         buf_len++;
2150     }
2151
2152         buf[buf_len] = NULLCHAR;
2153         next_out = leftover_len;
2154         leftover_start = 0;
2155
2156         i = 0;
2157         while (i < buf_len) {
2158             /* Deal with part of the TELNET option negotiation
2159                protocol.  We refuse to do anything beyond the
2160                defaults, except that we allow the WILL ECHO option,
2161                which ICS uses to turn off password echoing when we are
2162                directly connected to it.  We reject this option
2163                if localLineEditing mode is on (always on in xboard)
2164                and we are talking to port 23, which might be a real
2165                telnet server that will try to keep WILL ECHO on permanently.
2166              */
2167             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2168                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2169                 unsigned char option;
2170                 oldi = i;
2171                 switch ((unsigned char) buf[++i]) {
2172                   case TN_WILL:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<WILL ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       case TN_ECHO:
2177                         if (appData.debugMode)
2178                           fprintf(debugFP, "ECHO ");
2179                         /* Reply only if this is a change, according
2180                            to the protocol rules. */
2181                         if (remoteEchoOption) break;
2182                         if (appData.localLineEditing &&
2183                             atoi(appData.icsPort) == TN_PORT) {
2184                             TelnetRequest(TN_DONT, TN_ECHO);
2185                         } else {
2186                             EchoOff();
2187                             TelnetRequest(TN_DO, TN_ECHO);
2188                             remoteEchoOption = TRUE;
2189                         }
2190                         break;
2191                       default:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "%d ", option);
2194                         /* Whatever this is, we don't want it. */
2195                         TelnetRequest(TN_DONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_WONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<WONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       case TN_ECHO:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "ECHO ");
2206                         /* Reply only if this is a change, according
2207                            to the protocol rules. */
2208                         if (!remoteEchoOption) break;
2209                         EchoOn();
2210                         TelnetRequest(TN_DONT, TN_ECHO);
2211                         remoteEchoOption = FALSE;
2212                         break;
2213                       default:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "%d ", (unsigned char) option);
2216                         /* Whatever this is, it must already be turned
2217                            off, because we never agree to turn on
2218                            anything non-default, so according to the
2219                            protocol rules, we don't reply. */
2220                         break;
2221                     }
2222                     break;
2223                   case TN_DO:
2224                     if (appData.debugMode)
2225                       fprintf(debugFP, "\n<DO ");
2226                     switch (option = (unsigned char) buf[++i]) {
2227                       default:
2228                         /* Whatever this is, we refuse to do it. */
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         TelnetRequest(TN_WONT, option);
2232                         break;
2233                     }
2234                     break;
2235                   case TN_DONT:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<DONT ");
2238                     switch (option = (unsigned char) buf[++i]) {
2239                       default:
2240                         if (appData.debugMode)
2241                           fprintf(debugFP, "%d ", option);
2242                         /* Whatever this is, we are already not doing
2243                            it, because we never agree to do anything
2244                            non-default, so according to the protocol
2245                            rules, we don't reply. */
2246                         break;
2247                     }
2248                     break;
2249                   case TN_IAC:
2250                     if (appData.debugMode)
2251                       fprintf(debugFP, "\n<IAC ");
2252                     /* Doubled IAC; pass it through */
2253                     i--;
2254                     break;
2255                   default:
2256                     if (appData.debugMode)
2257                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2258                     /* Drop all other telnet commands on the floor */
2259                     break;
2260                 }
2261                 if (oldi > next_out)
2262                   SendToPlayer(&buf[next_out], oldi - next_out);
2263                 if (++i > next_out)
2264                   next_out = i;
2265                 continue;
2266             }
2267
2268             /* OK, this at least will *usually* work */
2269             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2270                 loggedOn = TRUE;
2271             }
2272
2273             if (loggedOn && !intfSet) {
2274                 if (ics_type == ICS_ICC) {
2275                   sprintf(str,
2276                           "/set-quietly interface %s\n/set-quietly style 12\n",
2277                           programVersion);
2278                 } else if (ics_type == ICS_CHESSNET) {
2279                   sprintf(str, "/style 12\n");
2280                 } else {
2281                   strcpy(str, "alias $ @\n$set interface ");
2282                   strcat(str, programVersion);
2283                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2284 #ifdef WIN32
2285                   strcat(str, "$iset nohighlight 1\n");
2286 #endif
2287                   strcat(str, "$iset lock 1\n$style 12\n");
2288                 }
2289                 SendToICS(str);
2290                 NotifyFrontendLogin();
2291                 intfSet = TRUE;
2292             }
2293
2294             if (started == STARTED_COMMENT) {
2295                 /* Accumulate characters in comment */
2296                 parse[parse_pos++] = buf[i];
2297                 if (buf[i] == '\n') {
2298                     parse[parse_pos] = NULLCHAR;
2299                     if(chattingPartner>=0) {
2300                         char mess[MSG_SIZ];
2301                         sprintf(mess, "%s%s", talker, parse);
2302                         OutputChatMessage(chattingPartner, mess);
2303                         chattingPartner = -1;
2304                     } else
2305                     if(!suppressKibitz) // [HGM] kibitz
2306                         AppendComment(forwardMostMove, StripHighlight(parse));
2307                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2308                         int nrDigit = 0, nrAlph = 0, i;
2309                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2310                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2311                         parse[parse_pos] = NULLCHAR;
2312                         // try to be smart: if it does not look like search info, it should go to
2313                         // ICS interaction window after all, not to engine-output window.
2314                         for(i=0; i<parse_pos; i++) { // count letters and digits
2315                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2316                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2317                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2318                         }
2319                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2320                             int depth=0; float score;
2321                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2322                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2323                                 pvInfoList[forwardMostMove-1].depth = depth;
2324                                 pvInfoList[forwardMostMove-1].score = 100*score;
2325                             }
2326                             OutputKibitz(suppressKibitz, parse);
2327                         } else {
2328                             char tmp[MSG_SIZ];
2329                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2330                             SendToPlayer(tmp, strlen(tmp));
2331                         }
2332                     }
2333                     started = STARTED_NONE;
2334                 } else {
2335                     /* Don't match patterns against characters in chatter */
2336                     i++;
2337                     continue;
2338                 }
2339             }
2340             if (started == STARTED_CHATTER) {
2341                 if (buf[i] != '\n') {
2342                     /* Don't match patterns against characters in chatter */
2343                     i++;
2344                     continue;
2345                 }
2346                 started = STARTED_NONE;
2347             }
2348
2349             /* Kludge to deal with rcmd protocol */
2350             if (firstTime && looking_at(buf, &i, "\001*")) {
2351                 DisplayFatalError(&buf[1], 0, 1);
2352                 continue;
2353             } else {
2354                 firstTime = FALSE;
2355             }
2356
2357             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2358                 ics_type = ICS_ICC;
2359                 ics_prefix = "/";
2360                 if (appData.debugMode)
2361                   fprintf(debugFP, "ics_type %d\n", ics_type);
2362                 continue;
2363             }
2364             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2365                 ics_type = ICS_FICS;
2366                 ics_prefix = "$";
2367                 if (appData.debugMode)
2368                   fprintf(debugFP, "ics_type %d\n", ics_type);
2369                 continue;
2370             }
2371             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2372                 ics_type = ICS_CHESSNET;
2373                 ics_prefix = "/";
2374                 if (appData.debugMode)
2375                   fprintf(debugFP, "ics_type %d\n", ics_type);
2376                 continue;
2377             }
2378
2379             if (!loggedOn &&
2380                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2381                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2382                  looking_at(buf, &i, "will be \"*\""))) {
2383               strcpy(ics_handle, star_match[0]);
2384               continue;
2385             }
2386
2387             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2388               char buf[MSG_SIZ];
2389               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2390               DisplayIcsInteractionTitle(buf);
2391               have_set_title = TRUE;
2392             }
2393
2394             /* skip finger notes */
2395             if (started == STARTED_NONE &&
2396                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2397                  (buf[i] == '1' && buf[i+1] == '0')) &&
2398                 buf[i+2] == ':' && buf[i+3] == ' ') {
2399               started = STARTED_CHATTER;
2400               i += 3;
2401               continue;
2402             }
2403
2404             /* skip formula vars */
2405             if (started == STARTED_NONE &&
2406                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2407               started = STARTED_CHATTER;
2408               i += 3;
2409               continue;
2410             }
2411
2412             oldi = i;
2413             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2414             if (appData.autoKibitz && started == STARTED_NONE &&
2415                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2416                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2417                 if(looking_at(buf, &i, "* kibitzes: ") &&
2418                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2419                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2420                         suppressKibitz = TRUE;
2421                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2422                                 && (gameMode == IcsPlayingWhite)) ||
2423                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2424                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2425                             started = STARTED_CHATTER; // own kibitz we simply discard
2426                         else {
2427                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2428                             parse_pos = 0; parse[0] = NULLCHAR;
2429                             savingComment = TRUE;
2430                             suppressKibitz = gameMode != IcsObserving ? 2 :
2431                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2432                         }
2433                         continue;
2434                 } else
2435                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2436                     started = STARTED_CHATTER;
2437                     suppressKibitz = TRUE;
2438                 }
2439             } // [HGM] kibitz: end of patch
2440
2441 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2442
2443             // [HGM] chat: intercept tells by users for which we have an open chat window
2444             channel = -1;
2445             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2446                                            looking_at(buf, &i, "* whispers:") ||
2447                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2448                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2449                 int p;
2450                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2451                 chattingPartner = -1;
2452
2453                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2454                 for(p=0; p<MAX_CHAT; p++) {
2455                     if(channel == atoi(chatPartner[p])) {
2456                     talker[0] = '['; strcat(talker, "]");
2457                     chattingPartner = p; break;
2458                     }
2459                 } else
2460                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2461                 for(p=0; p<MAX_CHAT; p++) {
2462                     if(!strcmp("WHISPER", chatPartner[p])) {
2463                         talker[0] = '['; strcat(talker, "]");
2464                         chattingPartner = p; break;
2465                     }
2466                 }
2467                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2468                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2469                     talker[0] = 0;
2470                     chattingPartner = p; break;
2471                 }
2472                 if(chattingPartner<0) i = oldi; else {
2473                     started = STARTED_COMMENT;
2474                     parse_pos = 0; parse[0] = NULLCHAR;
2475                     savingComment = TRUE;
2476                     suppressKibitz = TRUE;
2477                 }
2478             } // [HGM] chat: end of patch
2479
2480             if (appData.zippyTalk || appData.zippyPlay) {
2481                 /* [DM] Backup address for color zippy lines */
2482                 backup = i;
2483 #if ZIPPY
2484        #ifdef WIN32
2485                if (loggedOn == TRUE)
2486                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2487                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2488        #else
2489                 if (ZippyControl(buf, &i) ||
2490                     ZippyConverse(buf, &i) ||
2491                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2492                       loggedOn = TRUE;
2493                       if (!appData.colorize) continue;
2494                 }
2495        #endif
2496 #endif
2497             } // [DM] 'else { ' deleted
2498                 if (
2499                     /* Regular tells and says */
2500                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2501                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2502                     looking_at(buf, &i, "* says: ") ||
2503                     /* Don't color "message" or "messages" output */
2504                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2505                     looking_at(buf, &i, "*. * at *:*: ") ||
2506                     looking_at(buf, &i, "--* (*:*): ") ||
2507                     /* Message notifications (same color as tells) */
2508                     looking_at(buf, &i, "* has left a message ") ||
2509                     looking_at(buf, &i, "* just sent you a message:\n") ||
2510                     /* Whispers and kibitzes */
2511                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2512                     looking_at(buf, &i, "* kibitzes: ") ||
2513                     /* Channel tells */
2514                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2515
2516                   if (tkind == 1 && strchr(star_match[0], ':')) {
2517                       /* Avoid "tells you:" spoofs in channels */
2518                      tkind = 3;
2519                   }
2520                   if (star_match[0][0] == NULLCHAR ||
2521                       strchr(star_match[0], ' ') ||
2522                       (tkind == 3 && strchr(star_match[1], ' '))) {
2523                     /* Reject bogus matches */
2524                     i = oldi;
2525                   } else {
2526                     if (appData.colorize) {
2527                       if (oldi > next_out) {
2528                         SendToPlayer(&buf[next_out], oldi - next_out);
2529                         next_out = oldi;
2530                       }
2531                       switch (tkind) {
2532                       case 1:
2533                         Colorize(ColorTell, FALSE);
2534                         curColor = ColorTell;
2535                         break;
2536                       case 2:
2537                         Colorize(ColorKibitz, FALSE);
2538                         curColor = ColorKibitz;
2539                         break;
2540                       case 3:
2541                         p = strrchr(star_match[1], '(');
2542                         if (p == NULL) {
2543                           p = star_match[1];
2544                         } else {
2545                           p++;
2546                         }
2547                         if (atoi(p) == 1) {
2548                           Colorize(ColorChannel1, FALSE);
2549                           curColor = ColorChannel1;
2550                         } else {
2551                           Colorize(ColorChannel, FALSE);
2552                           curColor = ColorChannel;
2553                         }
2554                         break;
2555                       case 5:
2556                         curColor = ColorNormal;
2557                         break;
2558                       }
2559                     }
2560                     if (started == STARTED_NONE && appData.autoComment &&
2561                         (gameMode == IcsObserving ||
2562                          gameMode == IcsPlayingWhite ||
2563                          gameMode == IcsPlayingBlack)) {
2564                       parse_pos = i - oldi;
2565                       memcpy(parse, &buf[oldi], parse_pos);
2566                       parse[parse_pos] = NULLCHAR;
2567                       started = STARTED_COMMENT;
2568                       savingComment = TRUE;
2569                     } else {
2570                       started = STARTED_CHATTER;
2571                       savingComment = FALSE;
2572                     }
2573                     loggedOn = TRUE;
2574                     continue;
2575                   }
2576                 }
2577
2578                 if (looking_at(buf, &i, "* s-shouts: ") ||
2579                     looking_at(buf, &i, "* c-shouts: ")) {
2580                     if (appData.colorize) {
2581                         if (oldi > next_out) {
2582                             SendToPlayer(&buf[next_out], oldi - next_out);
2583                             next_out = oldi;
2584                         }
2585                         Colorize(ColorSShout, FALSE);
2586                         curColor = ColorSShout;
2587                     }
2588                     loggedOn = TRUE;
2589                     started = STARTED_CHATTER;
2590                     continue;
2591                 }
2592
2593                 if (looking_at(buf, &i, "--->")) {
2594                     loggedOn = TRUE;
2595                     continue;
2596                 }
2597
2598                 if (looking_at(buf, &i, "* shouts: ") ||
2599                     looking_at(buf, &i, "--> ")) {
2600                     if (appData.colorize) {
2601                         if (oldi > next_out) {
2602                             SendToPlayer(&buf[next_out], oldi - next_out);
2603                             next_out = oldi;
2604                         }
2605                         Colorize(ColorShout, FALSE);
2606                         curColor = ColorShout;
2607                     }
2608                     loggedOn = TRUE;
2609                     started = STARTED_CHATTER;
2610                     continue;
2611                 }
2612
2613                 if (looking_at( buf, &i, "Challenge:")) {
2614                     if (appData.colorize) {
2615                         if (oldi > next_out) {
2616                             SendToPlayer(&buf[next_out], oldi - next_out);
2617                             next_out = oldi;
2618                         }
2619                         Colorize(ColorChallenge, FALSE);
2620                         curColor = ColorChallenge;
2621                     }
2622                     loggedOn = TRUE;
2623                     continue;
2624                 }
2625
2626                 if (looking_at(buf, &i, "* offers you") ||
2627                     looking_at(buf, &i, "* offers to be") ||
2628                     looking_at(buf, &i, "* would like to") ||
2629                     looking_at(buf, &i, "* requests to") ||
2630                     looking_at(buf, &i, "Your opponent offers") ||
2631                     looking_at(buf, &i, "Your opponent requests")) {
2632
2633                     if (appData.colorize) {
2634                         if (oldi > next_out) {
2635                             SendToPlayer(&buf[next_out], oldi - next_out);
2636                             next_out = oldi;
2637                         }
2638                         Colorize(ColorRequest, FALSE);
2639                         curColor = ColorRequest;
2640                     }
2641                     continue;
2642                 }
2643
2644                 if (looking_at(buf, &i, "* (*) seeking")) {
2645                     if (appData.colorize) {
2646                         if (oldi > next_out) {
2647                             SendToPlayer(&buf[next_out], oldi - next_out);
2648                             next_out = oldi;
2649                         }
2650                         Colorize(ColorSeek, FALSE);
2651                         curColor = ColorSeek;
2652                     }
2653                     continue;
2654             }
2655
2656             if (looking_at(buf, &i, "\\   ")) {
2657                 if (prevColor != ColorNormal) {
2658                     if (oldi > next_out) {
2659                         SendToPlayer(&buf[next_out], oldi - next_out);
2660                         next_out = oldi;
2661                     }
2662                     Colorize(prevColor, TRUE);
2663                     curColor = prevColor;
2664                 }
2665                 if (savingComment) {
2666                     parse_pos = i - oldi;
2667                     memcpy(parse, &buf[oldi], parse_pos);
2668                     parse[parse_pos] = NULLCHAR;
2669                     started = STARTED_COMMENT;
2670                 } else {
2671                     started = STARTED_CHATTER;
2672                 }
2673                 continue;
2674             }
2675
2676             if (looking_at(buf, &i, "Black Strength :") ||
2677                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2678                 looking_at(buf, &i, "<10>") ||
2679                 looking_at(buf, &i, "#@#")) {
2680                 /* Wrong board style */
2681                 loggedOn = TRUE;
2682                 SendToICS(ics_prefix);
2683                 SendToICS("set style 12\n");
2684                 SendToICS(ics_prefix);
2685                 SendToICS("refresh\n");
2686                 continue;
2687             }
2688
2689             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2690                 ICSInitScript();
2691                 have_sent_ICS_logon = 1;
2692                 continue;
2693             }
2694
2695             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2696                 (looking_at(buf, &i, "\n<12> ") ||
2697                  looking_at(buf, &i, "<12> "))) {
2698                 loggedOn = TRUE;
2699                 if (oldi > next_out) {
2700                     SendToPlayer(&buf[next_out], oldi - next_out);
2701                 }
2702                 next_out = i;
2703                 started = STARTED_BOARD;
2704                 parse_pos = 0;
2705                 continue;
2706             }
2707
2708             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2709                 looking_at(buf, &i, "<b1> ")) {
2710                 if (oldi > next_out) {
2711                     SendToPlayer(&buf[next_out], oldi - next_out);
2712                 }
2713                 next_out = i;
2714                 started = STARTED_HOLDINGS;
2715                 parse_pos = 0;
2716                 continue;
2717             }
2718
2719             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2720                 loggedOn = TRUE;
2721                 /* Header for a move list -- first line */
2722
2723                 switch (ics_getting_history) {
2724                   case H_FALSE:
2725                     switch (gameMode) {
2726                       case IcsIdle:
2727                       case BeginningOfGame:
2728                         /* User typed "moves" or "oldmoves" while we
2729                            were idle.  Pretend we asked for these
2730                            moves and soak them up so user can step
2731                            through them and/or save them.
2732                            */
2733                         Reset(FALSE, TRUE);
2734                         gameMode = IcsObserving;
2735                         ModeHighlight();
2736                         ics_gamenum = -1;
2737                         ics_getting_history = H_GOT_UNREQ_HEADER;
2738                         break;
2739                       case EditGame: /*?*/
2740                       case EditPosition: /*?*/
2741                         /* Should above feature work in these modes too? */
2742                         /* For now it doesn't */
2743                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2744                         break;
2745                       default:
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                     }
2749                     break;
2750                   case H_REQUESTED:
2751                     /* Is this the right one? */
2752                     if (gameInfo.white && gameInfo.black &&
2753                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2754                         strcmp(gameInfo.black, star_match[2]) == 0) {
2755                         /* All is well */
2756                         ics_getting_history = H_GOT_REQ_HEADER;
2757                     }
2758                     break;
2759                   case H_GOT_REQ_HEADER:
2760                   case H_GOT_UNREQ_HEADER:
2761                   case H_GOT_UNWANTED_HEADER:
2762                   case H_GETTING_MOVES:
2763                     /* Should not happen */
2764                     DisplayError(_("Error gathering move list: two headers"), 0);
2765                     ics_getting_history = H_FALSE;
2766                     break;
2767                 }
2768
2769                 /* Save player ratings into gameInfo if needed */
2770                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2771                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2772                     (gameInfo.whiteRating == -1 ||
2773                      gameInfo.blackRating == -1)) {
2774
2775                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2776                     gameInfo.blackRating = string_to_rating(star_match[3]);
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2779                               gameInfo.whiteRating, gameInfo.blackRating);
2780                 }
2781                 continue;
2782             }
2783
2784             if (looking_at(buf, &i,
2785               "* * match, initial time: * minute*, increment: * second")) {
2786                 /* Header for a move list -- second line */
2787                 /* Initial board will follow if this is a wild game */
2788                 if (gameInfo.event != NULL) free(gameInfo.event);
2789                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2790                 gameInfo.event = StrSave(str);
2791                 /* [HGM] we switched variant. Translate boards if needed. */
2792                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2793                 continue;
2794             }
2795
2796             if (looking_at(buf, &i, "Move  ")) {
2797                 /* Beginning of a move list */
2798                 switch (ics_getting_history) {
2799                   case H_FALSE:
2800                     /* Normally should not happen */
2801                     /* Maybe user hit reset while we were parsing */
2802                     break;
2803                   case H_REQUESTED:
2804                     /* Happens if we are ignoring a move list that is not
2805                      * the one we just requested.  Common if the user
2806                      * tries to observe two games without turning off
2807                      * getMoveList */
2808                     break;
2809                   case H_GETTING_MOVES:
2810                     /* Should not happen */
2811                     DisplayError(_("Error gathering move list: nested"), 0);
2812                     ics_getting_history = H_FALSE;
2813                     break;
2814                   case H_GOT_REQ_HEADER:
2815                     ics_getting_history = H_GETTING_MOVES;
2816                     started = STARTED_MOVES;
2817                     parse_pos = 0;
2818                     if (oldi > next_out) {
2819                         SendToPlayer(&buf[next_out], oldi - next_out);
2820                     }
2821                     break;
2822                   case H_GOT_UNREQ_HEADER:
2823                     ics_getting_history = H_GETTING_MOVES;
2824                     started = STARTED_MOVES_NOHIDE;
2825                     parse_pos = 0;
2826                     break;
2827                   case H_GOT_UNWANTED_HEADER:
2828                     ics_getting_history = H_FALSE;
2829                     break;
2830                 }
2831                 continue;
2832             }
2833
2834             if (looking_at(buf, &i, "% ") ||
2835                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2836                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2837                 savingComment = FALSE;
2838                 switch (started) {
2839                   case STARTED_MOVES:
2840                   case STARTED_MOVES_NOHIDE:
2841                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2842                     parse[parse_pos + i - oldi] = NULLCHAR;
2843                     ParseGameHistory(parse);
2844 #if ZIPPY
2845                     if (appData.zippyPlay && first.initDone) {
2846                         FeedMovesToProgram(&first, forwardMostMove);
2847                         if (gameMode == IcsPlayingWhite) {
2848                             if (WhiteOnMove(forwardMostMove)) {
2849                                 if (first.sendTime) {
2850                                   if (first.useColors) {
2851                                     SendToProgram("black\n", &first);
2852                                   }
2853                                   SendTimeRemaining(&first, TRUE);
2854                                 }
2855                                 if (first.useColors) {
2856                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2857                                 }
2858                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2859                                 first.maybeThinking = TRUE;
2860                             } else {
2861                                 if (first.usePlayother) {
2862                                   if (first.sendTime) {
2863                                     SendTimeRemaining(&first, TRUE);
2864                                   }
2865                                   SendToProgram("playother\n", &first);
2866                                   firstMove = FALSE;
2867                                 } else {
2868                                   firstMove = TRUE;
2869                                 }
2870                             }
2871                         } else if (gameMode == IcsPlayingBlack) {
2872                             if (!WhiteOnMove(forwardMostMove)) {
2873                                 if (first.sendTime) {
2874                                   if (first.useColors) {
2875                                     SendToProgram("white\n", &first);
2876                                   }
2877                                   SendTimeRemaining(&first, FALSE);
2878                                 }
2879                                 if (first.useColors) {
2880                                   SendToProgram("black\n", &first);
2881                                 }
2882                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2883                                 first.maybeThinking = TRUE;
2884                             } else {
2885                                 if (first.usePlayother) {
2886                                   if (first.sendTime) {
2887                                     SendTimeRemaining(&first, FALSE);
2888                                   }
2889                                   SendToProgram("playother\n", &first);
2890                                   firstMove = FALSE;
2891                                 } else {
2892                                   firstMove = TRUE;
2893                                 }
2894                             }
2895                         }
2896                     }
2897 #endif
2898                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2899                         /* Moves came from oldmoves or moves command
2900                            while we weren't doing anything else.
2901                            */
2902                         currentMove = forwardMostMove;
2903                         ClearHighlights();/*!!could figure this out*/
2904                         flipView = appData.flipView;
2905                         DrawPosition(TRUE, boards[currentMove]);
2906                         DisplayBothClocks();
2907                         sprintf(str, "%s vs. %s",
2908                                 gameInfo.white, gameInfo.black);
2909                         DisplayTitle(str);
2910                         gameMode = IcsIdle;
2911                     } else {
2912                         /* Moves were history of an active game */
2913                         if (gameInfo.resultDetails != NULL) {
2914                             free(gameInfo.resultDetails);
2915                             gameInfo.resultDetails = NULL;
2916                         }
2917                     }
2918                     HistorySet(parseList, backwardMostMove,
2919                                forwardMostMove, currentMove-1);
2920                     DisplayMove(currentMove - 1);
2921                     if (started == STARTED_MOVES) next_out = i;
2922                     started = STARTED_NONE;
2923                     ics_getting_history = H_FALSE;
2924                     break;
2925
2926                   case STARTED_OBSERVE:
2927                     started = STARTED_NONE;
2928                     SendToICS(ics_prefix);
2929                     SendToICS("refresh\n");
2930                     break;
2931
2932                   default:
2933                     break;
2934                 }
2935                 if(bookHit) { // [HGM] book: simulate book reply
2936                     static char bookMove[MSG_SIZ]; // a bit generous?
2937
2938                     programStats.nodes = programStats.depth = programStats.time =
2939                     programStats.score = programStats.got_only_move = 0;
2940                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2941
2942                     strcpy(bookMove, "move ");
2943                     strcat(bookMove, bookHit);
2944                     HandleMachineMove(bookMove, &first);
2945                 }
2946                 continue;
2947             }
2948
2949             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2950                  started == STARTED_HOLDINGS ||
2951                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2952                 /* Accumulate characters in move list or board */
2953                 parse[parse_pos++] = buf[i];
2954             }
2955
2956             /* Start of game messages.  Mostly we detect start of game
2957                when the first board image arrives.  On some versions
2958                of the ICS, though, we need to do a "refresh" after starting
2959                to observe in order to get the current board right away. */
2960             if (looking_at(buf, &i, "Adding game * to observation list")) {
2961                 started = STARTED_OBSERVE;
2962                 continue;
2963             }
2964
2965             /* Handle auto-observe */
2966             if (appData.autoObserve &&
2967                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2968                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2969                 char *player;
2970                 /* Choose the player that was highlighted, if any. */
2971                 if (star_match[0][0] == '\033' ||
2972                     star_match[1][0] != '\033') {
2973                     player = star_match[0];
2974                 } else {
2975                     player = star_match[2];
2976                 }
2977                 sprintf(str, "%sobserve %s\n",
2978                         ics_prefix, StripHighlightAndTitle(player));
2979                 SendToICS(str);
2980
2981                 /* Save ratings from notify string */
2982                 strcpy(player1Name, star_match[0]);
2983                 player1Rating = string_to_rating(star_match[1]);
2984                 strcpy(player2Name, star_match[2]);
2985                 player2Rating = string_to_rating(star_match[3]);
2986
2987                 if (appData.debugMode)
2988                   fprintf(debugFP,
2989                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2990                           player1Name, player1Rating,
2991                           player2Name, player2Rating);
2992
2993                 continue;
2994             }
2995
2996             /* Deal with automatic examine mode after a game,
2997                and with IcsObserving -> IcsExamining transition */
2998             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2999                 looking_at(buf, &i, "has made you an examiner of game *")) {
3000
3001                 int gamenum = atoi(star_match[0]);
3002                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3003                     gamenum == ics_gamenum) {
3004                     /* We were already playing or observing this game;
3005                        no need to refetch history */
3006                     gameMode = IcsExamining;
3007                     if (pausing) {
3008                         pauseExamForwardMostMove = forwardMostMove;
3009                     } else if (currentMove < forwardMostMove) {
3010                         ForwardInner(forwardMostMove);
3011                     }
3012                 } else {
3013                     /* I don't think this case really can happen */
3014                     SendToICS(ics_prefix);
3015                     SendToICS("refresh\n");
3016                 }
3017                 continue;
3018             }
3019
3020             /* Error messages */
3021 //          if (ics_user_moved) {
3022             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3023                 if (looking_at(buf, &i, "Illegal move") ||
3024                     looking_at(buf, &i, "Not a legal move") ||
3025                     looking_at(buf, &i, "Your king is in check") ||
3026                     looking_at(buf, &i, "It isn't your turn") ||
3027                     looking_at(buf, &i, "It is not your move")) {
3028                     /* Illegal move */
3029                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3030                         currentMove = --forwardMostMove;
3031                         DisplayMove(currentMove - 1); /* before DMError */
3032                         DrawPosition(FALSE, boards[currentMove]);
3033                         SwitchClocks();
3034                         DisplayBothClocks();
3035                     }
3036                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037                     ics_user_moved = 0;
3038                     continue;
3039                 }
3040             }
3041
3042             if (looking_at(buf, &i, "still have time") ||
3043                 looking_at(buf, &i, "not out of time") ||
3044                 looking_at(buf, &i, "either player is out of time") ||
3045                 looking_at(buf, &i, "has timeseal; checking")) {
3046                 /* We must have called his flag a little too soon */
3047                 whiteFlag = blackFlag = FALSE;
3048                 continue;
3049             }
3050
3051             if (looking_at(buf, &i, "added * seconds to") ||
3052                 looking_at(buf, &i, "seconds were added to")) {
3053                 /* Update the clocks */
3054                 SendToICS(ics_prefix);
3055                 SendToICS("refresh\n");
3056                 continue;
3057             }
3058
3059             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3060                 ics_clock_paused = TRUE;
3061                 StopClocks();
3062                 continue;
3063             }
3064
3065             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3066                 ics_clock_paused = FALSE;
3067                 StartClocks();
3068                 continue;
3069             }
3070
3071             /* Grab player ratings from the Creating: message.
3072                Note we have to check for the special case when
3073                the ICS inserts things like [white] or [black]. */
3074             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3075                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3076                 /* star_matches:
3077                    0    player 1 name (not necessarily white)
3078                    1    player 1 rating
3079                    2    empty, white, or black (IGNORED)
3080                    3    player 2 name (not necessarily black)
3081                    4    player 2 rating
3082
3083                    The names/ratings are sorted out when the game
3084                    actually starts (below).
3085                 */
3086                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3087                 player1Rating = string_to_rating(star_match[1]);
3088                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3089                 player2Rating = string_to_rating(star_match[4]);
3090
3091                 if (appData.debugMode)
3092                   fprintf(debugFP,
3093                           "Ratings from 'Creating:' %s %d, %s %d\n",
3094                           player1Name, player1Rating,
3095                           player2Name, player2Rating);
3096
3097                 continue;
3098             }
3099
3100             /* Improved generic start/end-of-game messages */
3101             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3102                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3103                 /* If tkind == 0: */
3104                 /* star_match[0] is the game number */
3105                 /*           [1] is the white player's name */
3106                 /*           [2] is the black player's name */
3107                 /* For end-of-game: */
3108                 /*           [3] is the reason for the game end */
3109                 /*           [4] is a PGN end game-token, preceded by " " */
3110                 /* For start-of-game: */
3111                 /*           [3] begins with "Creating" or "Continuing" */
3112                 /*           [4] is " *" or empty (don't care). */
3113                 int gamenum = atoi(star_match[0]);
3114                 char *whitename, *blackname, *why, *endtoken;
3115                 ChessMove endtype = (ChessMove) 0;
3116
3117                 if (tkind == 0) {
3118                   whitename = star_match[1];
3119                   blackname = star_match[2];
3120                   why = star_match[3];
3121                   endtoken = star_match[4];
3122                 } else {
3123                   whitename = star_match[1];
3124                   blackname = star_match[3];
3125                   why = star_match[5];
3126                   endtoken = star_match[6];
3127                 }
3128
3129                 /* Game start messages */
3130                 if (strncmp(why, "Creating ", 9) == 0 ||
3131                     strncmp(why, "Continuing ", 11) == 0) {
3132                     gs_gamenum = gamenum;
3133                     strcpy(gs_kind, strchr(why, ' ') + 1);
3134 #if ZIPPY
3135                     if (appData.zippyPlay) {
3136                         ZippyGameStart(whitename, blackname);
3137                     }
3138 #endif /*ZIPPY*/
3139                     continue;
3140                 }
3141
3142                 /* Game end messages */
3143                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3144                     ics_gamenum != gamenum) {
3145                     continue;
3146                 }
3147                 while (endtoken[0] == ' ') endtoken++;
3148                 switch (endtoken[0]) {
3149                   case '*':
3150                   default:
3151                     endtype = GameUnfinished;
3152                     break;
3153                   case '0':
3154                     endtype = BlackWins;
3155                     break;
3156                   case '1':
3157                     if (endtoken[1] == '/')
3158                       endtype = GameIsDrawn;
3159                     else
3160                       endtype = WhiteWins;
3161                     break;
3162                 }
3163                 GameEnds(endtype, why, GE_ICS);
3164 #if ZIPPY
3165                 if (appData.zippyPlay && first.initDone) {
3166                     ZippyGameEnd(endtype, why);
3167                     if (first.pr == NULL) {
3168                       /* Start the next process early so that we'll
3169                          be ready for the next challenge */
3170                       StartChessProgram(&first);
3171                     }
3172                     /* Send "new" early, in case this command takes
3173                        a long time to finish, so that we'll be ready
3174                        for the next challenge. */
3175                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3176                     Reset(TRUE, TRUE);
3177                 }
3178 #endif /*ZIPPY*/
3179                 continue;
3180             }
3181
3182             if (looking_at(buf, &i, "Removing game * from observation") ||
3183                 looking_at(buf, &i, "no longer observing game *") ||
3184                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3185                 if (gameMode == IcsObserving &&
3186                     atoi(star_match[0]) == ics_gamenum)
3187                   {
3188                       /* icsEngineAnalyze */
3189                       if (appData.icsEngineAnalyze) {
3190                             ExitAnalyzeMode();
3191                             ModeHighlight();
3192                       }
3193                       StopClocks();
3194                       gameMode = IcsIdle;
3195                       ics_gamenum = -1;
3196                       ics_user_moved = FALSE;
3197                   }
3198                 continue;
3199             }
3200
3201             if (looking_at(buf, &i, "no longer examining game *")) {
3202                 if (gameMode == IcsExamining &&
3203                     atoi(star_match[0]) == ics_gamenum)
3204                   {
3205                       gameMode = IcsIdle;
3206                       ics_gamenum = -1;
3207                       ics_user_moved = FALSE;
3208                   }
3209                 continue;
3210             }
3211
3212             /* Advance leftover_start past any newlines we find,
3213                so only partial lines can get reparsed */
3214             if (looking_at(buf, &i, "\n")) {
3215                 prevColor = curColor;
3216                 if (curColor != ColorNormal) {
3217                     if (oldi > next_out) {
3218                         SendToPlayer(&buf[next_out], oldi - next_out);
3219                         next_out = oldi;
3220                     }
3221                     Colorize(ColorNormal, FALSE);
3222                     curColor = ColorNormal;
3223                 }
3224                 if (started == STARTED_BOARD) {
3225                     started = STARTED_NONE;
3226                     parse[parse_pos] = NULLCHAR;
3227                     ParseBoard12(parse);
3228                     ics_user_moved = 0;
3229
3230                     /* Send premove here */
3231                     if (appData.premove) {
3232                       char str[MSG_SIZ];
3233                       if (currentMove == 0 &&
3234                           gameMode == IcsPlayingWhite &&
3235                           appData.premoveWhite) {
3236                         sprintf(str, "%s\n", appData.premoveWhiteText);
3237                         if (appData.debugMode)
3238                           fprintf(debugFP, "Sending premove:\n");
3239                         SendToICS(str);
3240                       } else if (currentMove == 1 &&
3241                                  gameMode == IcsPlayingBlack &&
3242                                  appData.premoveBlack) {
3243                         sprintf(str, "%s\n", appData.premoveBlackText);
3244                         if (appData.debugMode)
3245                           fprintf(debugFP, "Sending premove:\n");
3246                         SendToICS(str);
3247                       } else if (gotPremove) {
3248                         gotPremove = 0;
3249                         ClearPremoveHighlights();
3250                         if (appData.debugMode)
3251                           fprintf(debugFP, "Sending premove:\n");
3252                           UserMoveEvent(premoveFromX, premoveFromY,
3253                                         premoveToX, premoveToY,
3254                                         premovePromoChar);
3255                       }
3256                     }
3257
3258                     /* Usually suppress following prompt */
3259                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3260                         if (looking_at(buf, &i, "*% ")) {
3261                             savingComment = FALSE;
3262                         }
3263                     }
3264                     next_out = i;
3265                 } else if (started == STARTED_HOLDINGS) {
3266                     int gamenum;
3267                     char new_piece[MSG_SIZ];
3268                     started = STARTED_NONE;
3269                     parse[parse_pos] = NULLCHAR;
3270                     if (appData.debugMode)
3271                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3272                                                         parse, currentMove);
3273                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3274                         gamenum == ics_gamenum) {
3275                         if (gameInfo.variant == VariantNormal) {
3276                           /* [HGM] We seem to switch variant during a game!
3277                            * Presumably no holdings were displayed, so we have
3278                            * to move the position two files to the right to
3279                            * create room for them!
3280                            */
3281                           VariantClass newVariant;
3282                           switch(gameInfo.boardWidth) { // base guess on board width
3283                                 case 9:  newVariant = VariantShogi; break;
3284                                 case 10: newVariant = VariantGreat; break;
3285                                 default: newVariant = VariantCrazyhouse; break;
3286                           }
3287                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3288                           /* Get a move list just to see the header, which
3289                              will tell us whether this is really bug or zh */
3290                           if (ics_getting_history == H_FALSE) {
3291                             ics_getting_history = H_REQUESTED;
3292                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3293                             SendToICS(str);
3294                           }
3295                         }
3296                         new_piece[0] = NULLCHAR;
3297                         sscanf(parse, "game %d white [%s black [%s <- %s",
3298                                &gamenum, white_holding, black_holding,
3299                                new_piece);
3300                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3301                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3302                         /* [HGM] copy holdings to board holdings area */
3303                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3304                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3305                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3306 #if ZIPPY
3307                         if (appData.zippyPlay && first.initDone) {
3308                             ZippyHoldings(white_holding, black_holding,
3309                                           new_piece);
3310                         }
3311 #endif /*ZIPPY*/
3312                         if (tinyLayout || smallLayout) {
3313                             char wh[16], bh[16];
3314                             PackHolding(wh, white_holding);
3315                             PackHolding(bh, black_holding);
3316                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3317                                     gameInfo.white, gameInfo.black);
3318                         } else {
3319                             sprintf(str, "%s [%s] vs. %s [%s]",
3320                                     gameInfo.white, white_holding,
3321                                     gameInfo.black, black_holding);
3322                         }
3323
3324                         DrawPosition(FALSE, boards[currentMove]);
3325                         DisplayTitle(str);
3326                     }
3327                     /* Suppress following prompt */
3328                     if (looking_at(buf, &i, "*% ")) {
3329                         savingComment = FALSE;
3330                     }
3331                     next_out = i;
3332                 }
3333                 continue;
3334             }
3335
3336             i++;                /* skip unparsed character and loop back */
3337         }
3338
3339         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3340             started != STARTED_HOLDINGS && i > next_out) {
3341             SendToPlayer(&buf[next_out], i - next_out);
3342             next_out = i;
3343         }
3344         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3345
3346         leftover_len = buf_len - leftover_start;
3347         /* if buffer ends with something we couldn't parse,
3348            reparse it after appending the next read */
3349
3350     } else if (count == 0) {
3351         RemoveInputSource(isr);
3352         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3353     } else {
3354         DisplayFatalError(_("Error reading from ICS"), error, 1);
3355     }
3356 }
3357
3358
3359 /* Board style 12 looks like this:
3360
3361    <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
3362
3363  * The "<12> " is stripped before it gets to this routine.  The two
3364  * trailing 0's (flip state and clock ticking) are later addition, and
3365  * some chess servers may not have them, or may have only the first.
3366  * Additional trailing fields may be added in the future.
3367  */
3368
3369 #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"
3370
3371 #define RELATION_OBSERVING_PLAYED    0
3372 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3373 #define RELATION_PLAYING_MYMOVE      1
3374 #define RELATION_PLAYING_NOTMYMOVE  -1
3375 #define RELATION_EXAMINING           2
3376 #define RELATION_ISOLATED_BOARD     -3
3377 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3378
3379 void
3380 ParseBoard12(string)
3381      char *string;
3382 {
3383     GameMode newGameMode;
3384     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3385     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3386     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3387     char to_play, board_chars[200];
3388     char move_str[500], str[500], elapsed_time[500];
3389     char black[32], white[32];
3390     Board board;
3391     int prevMove = currentMove;
3392     int ticking = 2;
3393     ChessMove moveType;
3394     int fromX, fromY, toX, toY;
3395     char promoChar;
3396     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3397     char *bookHit = NULL; // [HGM] book
3398     Boolean weird = FALSE;
3399
3400     fromX = fromY = toX = toY = -1;
3401
3402     newGame = FALSE;
3403
3404     if (appData.debugMode)
3405       fprintf(debugFP, _("Parsing board: %s\n"), string);
3406
3407     move_str[0] = NULLCHAR;
3408     elapsed_time[0] = NULLCHAR;
3409     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3410         int  i = 0, j;
3411         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3412             if(string[i] == ' ') { ranks++; files = 0; }
3413             else files++;
3414             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3415             i++;
3416         }
3417         for(j = 0; j <i; j++) board_chars[j] = string[j];
3418         board_chars[i] = '\0';
3419         string += i + 1;
3420     }
3421     n = sscanf(string, PATTERN, &to_play, &double_push,
3422                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3423                &gamenum, white, black, &relation, &basetime, &increment,
3424                &white_stren, &black_stren, &white_time, &black_time,
3425                &moveNum, str, elapsed_time, move_str, &ics_flip,
3426                &ticking);
3427
3428    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3429                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3430      /* [HGM] We seem to switch variant during a game!
3431       * Try to guess new variant from board size
3432       */
3433           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3434           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3435           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3436           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3437           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3438           if(!weird) newVariant = VariantNormal;
3439           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3440           /* Get a move list just to see the header, which
3441              will tell us whether this is really bug or zh */
3442           if (ics_getting_history == H_FALSE) {
3443             ics_getting_history = H_REQUESTED;
3444             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3445             SendToICS(str);
3446           }
3447     }
3448
3449     if (n < 21) {
3450         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3451         DisplayError(str, 0);
3452         return;
3453     }
3454
3455     /* Convert the move number to internal form */
3456     moveNum = (moveNum - 1) * 2;
3457     if (to_play == 'B') moveNum++;
3458     if (moveNum >= MAX_MOVES) {
3459       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3460                         0, 1);
3461       return;
3462     }
3463
3464     switch (relation) {
3465       case RELATION_OBSERVING_PLAYED:
3466       case RELATION_OBSERVING_STATIC:
3467         if (gamenum == -1) {
3468             /* Old ICC buglet */
3469             relation = RELATION_OBSERVING_STATIC;
3470         }
3471         newGameMode = IcsObserving;
3472         break;
3473       case RELATION_PLAYING_MYMOVE:
3474       case RELATION_PLAYING_NOTMYMOVE:
3475         newGameMode =
3476           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3477             IcsPlayingWhite : IcsPlayingBlack;
3478         break;
3479       case RELATION_EXAMINING:
3480         newGameMode = IcsExamining;
3481         break;
3482       case RELATION_ISOLATED_BOARD:
3483       default:
3484         /* Just display this board.  If user was doing something else,
3485            we will forget about it until the next board comes. */
3486         newGameMode = IcsIdle;
3487         break;
3488       case RELATION_STARTING_POSITION:
3489         newGameMode = gameMode;
3490         break;
3491     }
3492
3493     /* Modify behavior for initial board display on move listing
3494        of wild games.
3495        */
3496     switch (ics_getting_history) {
3497       case H_FALSE:
3498       case H_REQUESTED:
3499         break;
3500       case H_GOT_REQ_HEADER:
3501       case H_GOT_UNREQ_HEADER:
3502         /* This is the initial position of the current game */
3503         gamenum = ics_gamenum;
3504         moveNum = 0;            /* old ICS bug workaround */
3505         if (to_play == 'B') {
3506           startedFromSetupPosition = TRUE;
3507           blackPlaysFirst = TRUE;
3508           moveNum = 1;
3509           if (forwardMostMove == 0) forwardMostMove = 1;
3510           if (backwardMostMove == 0) backwardMostMove = 1;
3511           if (currentMove == 0) currentMove = 1;
3512         }
3513         newGameMode = gameMode;
3514         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3515         break;
3516       case H_GOT_UNWANTED_HEADER:
3517         /* This is an initial board that we don't want */
3518         return;
3519       case H_GETTING_MOVES:
3520         /* Should not happen */
3521         DisplayError(_("Error gathering move list: extra board"), 0);
3522         ics_getting_history = H_FALSE;
3523         return;
3524     }
3525
3526     /* Take action if this is the first board of a new game, or of a
3527        different game than is currently being displayed.  */
3528     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3529         relation == RELATION_ISOLATED_BOARD) {
3530
3531         /* Forget the old game and get the history (if any) of the new one */
3532         if (gameMode != BeginningOfGame) {
3533           Reset(TRUE, TRUE);
3534         }
3535         newGame = TRUE;
3536         if (appData.autoRaiseBoard) BoardToTop();
3537         prevMove = -3;
3538         if (gamenum == -1) {
3539             newGameMode = IcsIdle;
3540         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3541                    appData.getMoveList) {
3542             /* Need to get game history */
3543             ics_getting_history = H_REQUESTED;
3544             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3545             SendToICS(str);
3546         }
3547
3548         /* Initially flip the board to have black on the bottom if playing
3549            black or if the ICS flip flag is set, but let the user change
3550            it with the Flip View button. */
3551         flipView = appData.autoFlipView ?
3552           (newGameMode == IcsPlayingBlack) || ics_flip :
3553           appData.flipView;
3554
3555         /* Done with values from previous mode; copy in new ones */
3556         gameMode = newGameMode;
3557         ModeHighlight();
3558         ics_gamenum = gamenum;
3559         if (gamenum == gs_gamenum) {
3560             int klen = strlen(gs_kind);
3561             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3562             sprintf(str, "ICS %s", gs_kind);
3563             gameInfo.event = StrSave(str);
3564         } else {
3565             gameInfo.event = StrSave("ICS game");
3566         }
3567         gameInfo.site = StrSave(appData.icsHost);
3568         gameInfo.date = PGNDate();
3569         gameInfo.round = StrSave("-");
3570         gameInfo.white = StrSave(white);
3571         gameInfo.black = StrSave(black);
3572         timeControl = basetime * 60 * 1000;
3573         timeControl_2 = 0;
3574         timeIncrement = increment * 1000;
3575         movesPerSession = 0;
3576         gameInfo.timeControl = TimeControlTagValue();
3577         VariantSwitch(board, StringToVariant(gameInfo.event) );
3578   if (appData.debugMode) {
3579     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3580     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3581     setbuf(debugFP, NULL);
3582   }
3583
3584         gameInfo.outOfBook = NULL;
3585
3586         /* Do we have the ratings? */
3587         if (strcmp(player1Name, white) == 0 &&
3588             strcmp(player2Name, black) == 0) {
3589             if (appData.debugMode)
3590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3591                       player1Rating, player2Rating);
3592             gameInfo.whiteRating = player1Rating;
3593             gameInfo.blackRating = player2Rating;
3594         } else if (strcmp(player2Name, white) == 0 &&
3595                    strcmp(player1Name, black) == 0) {
3596             if (appData.debugMode)
3597               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3598                       player2Rating, player1Rating);
3599             gameInfo.whiteRating = player2Rating;
3600             gameInfo.blackRating = player1Rating;
3601         }
3602         player1Name[0] = player2Name[0] = NULLCHAR;
3603
3604         /* Silence shouts if requested */
3605         if (appData.quietPlay &&
3606             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3607             SendToICS(ics_prefix);
3608             SendToICS("set shout 0\n");
3609         }
3610     }
3611
3612     /* Deal with midgame name changes */
3613     if (!newGame) {
3614         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3615             if (gameInfo.white) free(gameInfo.white);
3616             gameInfo.white = StrSave(white);
3617         }
3618         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3619             if (gameInfo.black) free(gameInfo.black);
3620             gameInfo.black = StrSave(black);
3621         }
3622     }
3623
3624     /* Throw away game result if anything actually changes in examine mode */
3625     if (gameMode == IcsExamining && !newGame) {
3626         gameInfo.result = GameUnfinished;
3627         if (gameInfo.resultDetails != NULL) {
3628             free(gameInfo.resultDetails);
3629             gameInfo.resultDetails = NULL;
3630         }
3631     }
3632
3633     /* In pausing && IcsExamining mode, we ignore boards coming
3634        in if they are in a different variation than we are. */
3635     if (pauseExamInvalid) return;
3636     if (pausing && gameMode == IcsExamining) {
3637         if (moveNum <= pauseExamForwardMostMove) {
3638             pauseExamInvalid = TRUE;
3639             forwardMostMove = pauseExamForwardMostMove;
3640             return;
3641         }
3642     }
3643
3644   if (appData.debugMode) {
3645     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3646   }
3647     /* Parse the board */
3648     for (k = 0; k < ranks; k++) {
3649       for (j = 0; j < files; j++)
3650         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3651       if(gameInfo.holdingsWidth > 1) {
3652            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3653            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3654       }
3655     }
3656     CopyBoard(boards[moveNum], board);
3657     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3658     if (moveNum == 0) {
3659         startedFromSetupPosition =
3660           !CompareBoards(board, initialPosition);
3661         if(startedFromSetupPosition)
3662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3663     }
3664
3665     /* [HGM] Set castling rights. Take the outermost Rooks,
3666        to make it also work for FRC opening positions. Note that board12
3667        is really defective for later FRC positions, as it has no way to
3668        indicate which Rook can castle if they are on the same side of King.
3669        For the initial position we grant rights to the outermost Rooks,
3670        and remember thos rights, and we then copy them on positions
3671        later in an FRC game. This means WB might not recognize castlings with
3672        Rooks that have moved back to their original position as illegal,
3673        but in ICS mode that is not its job anyway.
3674     */
3675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3677
3678         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3679             if(board[0][i] == WhiteRook) j = i;
3680         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3681         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3682             if(board[0][i] == WhiteRook) j = i;
3683         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3684         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3686         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3689         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3690
3691         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3692         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3693             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3694         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3695             if(board[BOARD_HEIGHT-1][k] == bKing)
3696                 initialRights[5] = castlingRights[moveNum][5] = k;
3697     } else { int r;
3698         r = castlingRights[moveNum][0] = initialRights[0];
3699         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3700         r = castlingRights[moveNum][1] = initialRights[1];
3701         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3702         r = castlingRights[moveNum][3] = initialRights[3];
3703         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3704         r = castlingRights[moveNum][4] = initialRights[4];
3705         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3706         /* wildcastle kludge: always assume King has rights */
3707         r = castlingRights[moveNum][2] = initialRights[2];
3708         r = castlingRights[moveNum][5] = initialRights[5];
3709     }
3710     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3711     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3712
3713
3714     if (ics_getting_history == H_GOT_REQ_HEADER ||
3715         ics_getting_history == H_GOT_UNREQ_HEADER) {
3716         /* This was an initial position from a move list, not
3717            the current position */
3718         return;
3719     }
3720
3721     /* Update currentMove and known move number limits */
3722     newMove = newGame || moveNum > forwardMostMove;
3723
3724     if (newGame) {
3725         forwardMostMove = backwardMostMove = currentMove = moveNum;
3726         if (gameMode == IcsExamining && moveNum == 0) {
3727           /* Workaround for ICS limitation: we are not told the wild
3728              type when starting to examine a game.  But if we ask for
3729              the move list, the move list header will tell us */
3730             ics_getting_history = H_REQUESTED;
3731             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3732             SendToICS(str);
3733         }
3734     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3735                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3736 #if ZIPPY
3737         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3738         /* [HGM] applied this also to an engine that is silently watching        */
3739         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3740             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3741             gameInfo.variant == currentlyInitializedVariant) {
3742           takeback = forwardMostMove - moveNum;
3743           for (i = 0; i < takeback; i++) {
3744             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3745             SendToProgram("undo\n", &first);
3746           }
3747         }
3748 #endif
3749
3750         forwardMostMove = moveNum;
3751         if (!pausing || currentMove > forwardMostMove)
3752           currentMove = forwardMostMove;
3753     } else {
3754         /* New part of history that is not contiguous with old part */
3755         if (pausing && gameMode == IcsExamining) {
3756             pauseExamInvalid = TRUE;
3757             forwardMostMove = pauseExamForwardMostMove;
3758             return;
3759         }
3760         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3761 #if ZIPPY
3762             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3763                 // [HGM] when we will receive the move list we now request, it will be
3764                 // fed to the engine from the first move on. So if the engine is not
3765                 // in the initial position now, bring it there.
3766                 InitChessProgram(&first, 0);
3767             }
3768 #endif
3769             ics_getting_history = H_REQUESTED;
3770             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3771             SendToICS(str);
3772         }
3773         forwardMostMove = backwardMostMove = currentMove = moveNum;
3774     }
3775
3776     /* Update the clocks */
3777     if (strchr(elapsed_time, '.')) {
3778       /* Time is in ms */
3779       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3780       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3781     } else {
3782       /* Time is in seconds */
3783       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3784       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3785     }
3786
3787
3788 #if ZIPPY
3789     if (appData.zippyPlay && newGame &&
3790         gameMode != IcsObserving && gameMode != IcsIdle &&
3791         gameMode != IcsExamining)
3792       ZippyFirstBoard(moveNum, basetime, increment);
3793 #endif
3794
3795     /* Put the move on the move list, first converting
3796        to canonical algebraic form. */
3797     if (moveNum > 0) {
3798   if (appData.debugMode) {
3799     if (appData.debugMode) { int f = forwardMostMove;
3800         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3801                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3802     }
3803     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3804     fprintf(debugFP, "moveNum = %d\n", moveNum);
3805     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3806     setbuf(debugFP, NULL);
3807   }
3808         if (moveNum <= backwardMostMove) {
3809             /* We don't know what the board looked like before
3810                this move.  Punt. */
3811             strcpy(parseList[moveNum - 1], move_str);
3812             strcat(parseList[moveNum - 1], " ");
3813             strcat(parseList[moveNum - 1], elapsed_time);
3814             moveList[moveNum - 1][0] = NULLCHAR;
3815         } else if (strcmp(move_str, "none") == 0) {
3816             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3817             /* Again, we don't know what the board looked like;
3818                this is really the start of the game. */
3819             parseList[moveNum - 1][0] = NULLCHAR;
3820             moveList[moveNum - 1][0] = NULLCHAR;
3821             backwardMostMove = moveNum;
3822             startedFromSetupPosition = TRUE;
3823             fromX = fromY = toX = toY = -1;
3824         } else {
3825           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3826           //                 So we parse the long-algebraic move string in stead of the SAN move
3827           int valid; char buf[MSG_SIZ], *prom;
3828
3829           // str looks something like "Q/a1-a2"; kill the slash
3830           if(str[1] == '/')
3831                 sprintf(buf, "%c%s", str[0], str+2);
3832           else  strcpy(buf, str); // might be castling
3833           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3834                 strcat(buf, prom); // long move lacks promo specification!
3835           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3836                 if(appData.debugMode)
3837                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3838                 strcpy(move_str, buf);
3839           }
3840           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3841                                 &fromX, &fromY, &toX, &toY, &promoChar)
3842                || ParseOneMove(buf, moveNum - 1, &moveType,
3843                                 &fromX, &fromY, &toX, &toY, &promoChar);
3844           // end of long SAN patch
3845           if (valid) {
3846             (void) CoordsToAlgebraic(boards[moveNum - 1],
3847                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3848                                      fromY, fromX, toY, toX, promoChar,
3849                                      parseList[moveNum-1]);
3850             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3851                              castlingRights[moveNum]) ) {
3852               case MT_NONE:
3853               case MT_STALEMATE:
3854               default:
3855                 break;
3856               case MT_CHECK:
3857                 if(gameInfo.variant != VariantShogi)
3858                     strcat(parseList[moveNum - 1], "+");
3859                 break;
3860               case MT_CHECKMATE:
3861               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3862                 strcat(parseList[moveNum - 1], "#");
3863                 break;
3864             }
3865             strcat(parseList[moveNum - 1], " ");
3866             strcat(parseList[moveNum - 1], elapsed_time);
3867             /* currentMoveString is set as a side-effect of ParseOneMove */
3868             strcpy(moveList[moveNum - 1], currentMoveString);
3869             strcat(moveList[moveNum - 1], "\n");
3870           } else {
3871             /* Move from ICS was illegal!?  Punt. */
3872   if (appData.debugMode) {
3873     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3874     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3875   }
3876             strcpy(parseList[moveNum - 1], move_str);
3877             strcat(parseList[moveNum - 1], " ");
3878             strcat(parseList[moveNum - 1], elapsed_time);
3879             moveList[moveNum - 1][0] = NULLCHAR;
3880             fromX = fromY = toX = toY = -1;
3881           }
3882         }
3883   if (appData.debugMode) {
3884     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3885     setbuf(debugFP, NULL);
3886   }
3887
3888 #if ZIPPY
3889         /* Send move to chess program (BEFORE animating it). */
3890         if (appData.zippyPlay && !newGame && newMove &&
3891            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3892
3893             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3894                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3895                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3896                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3897                             move_str);
3898                     DisplayError(str, 0);
3899                 } else {
3900                     if (first.sendTime) {
3901                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3902                     }
3903                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3904                     if (firstMove && !bookHit) {
3905                         firstMove = FALSE;
3906                         if (first.useColors) {
3907                           SendToProgram(gameMode == IcsPlayingWhite ?
3908                                         "white\ngo\n" :
3909                                         "black\ngo\n", &first);
3910                         } else {
3911                           SendToProgram("go\n", &first);
3912                         }
3913                         first.maybeThinking = TRUE;
3914                     }
3915                 }
3916             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3917               if (moveList[moveNum - 1][0] == NULLCHAR) {
3918                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3919                 DisplayError(str, 0);
3920               } else {
3921                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3922                 SendMoveToProgram(moveNum - 1, &first);
3923               }
3924             }
3925         }
3926 #endif
3927     }
3928
3929     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3930         /* If move comes from a remote source, animate it.  If it
3931            isn't remote, it will have already been animated. */
3932         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3933             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3934         }
3935         if (!pausing && appData.highlightLastMove) {
3936             SetHighlights(fromX, fromY, toX, toY);
3937         }
3938     }
3939
3940     /* Start the clocks */
3941     whiteFlag = blackFlag = FALSE;
3942     appData.clockMode = !(basetime == 0 && increment == 0);
3943     if (ticking == 0) {
3944       ics_clock_paused = TRUE;
3945       StopClocks();
3946     } else if (ticking == 1) {
3947       ics_clock_paused = FALSE;
3948     }
3949     if (gameMode == IcsIdle ||
3950         relation == RELATION_OBSERVING_STATIC ||
3951         relation == RELATION_EXAMINING ||
3952         ics_clock_paused)
3953       DisplayBothClocks();
3954     else
3955       StartClocks();
3956
3957     /* Display opponents and material strengths */
3958     if (gameInfo.variant != VariantBughouse &&
3959         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3960         if (tinyLayout || smallLayout) {
3961             if(gameInfo.variant == VariantNormal)
3962                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3963                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3964                     basetime, increment);
3965             else
3966                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3967                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3968                     basetime, increment, (int) gameInfo.variant);
3969         } else {
3970             if(gameInfo.variant == VariantNormal)
3971                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3972                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3973                     basetime, increment);
3974             else
3975                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3976                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3977                     basetime, increment, VariantName(gameInfo.variant));
3978         }
3979         DisplayTitle(str);
3980   if (appData.debugMode) {
3981     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3982   }
3983     }
3984
3985
3986     /* Display the board */
3987     if (!pausing && !appData.noGUI) {
3988       if (appData.premove)
3989           if (!gotPremove ||
3990              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3991              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3992               ClearPremoveHighlights();
3993
3994       DrawPosition(FALSE, boards[currentMove]);
3995       DisplayMove(moveNum - 1);
3996       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3997             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3998               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3999         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4000       }
4001     }
4002
4003     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4004 #if ZIPPY
4005     if(bookHit) { // [HGM] book: simulate book reply
4006         static char bookMove[MSG_SIZ]; // a bit generous?
4007
4008         programStats.nodes = programStats.depth = programStats.time =
4009         programStats.score = programStats.got_only_move = 0;
4010         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4011
4012         strcpy(bookMove, "move ");
4013         strcat(bookMove, bookHit);
4014         HandleMachineMove(bookMove, &first);
4015     }
4016 #endif
4017 }
4018
4019 void
4020 GetMoveListEvent()
4021 {
4022     char buf[MSG_SIZ];
4023     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4024         ics_getting_history = H_REQUESTED;
4025         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4026         SendToICS(buf);
4027     }
4028 }
4029
4030 void
4031 AnalysisPeriodicEvent(force)
4032      int force;
4033 {
4034     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4035          && !force) || !appData.periodicUpdates)
4036       return;
4037
4038     /* Send . command to Crafty to collect stats */
4039     SendToProgram(".\n", &first);
4040
4041     /* Don't send another until we get a response (this makes
4042        us stop sending to old Crafty's which don't understand
4043        the "." command (sending illegal cmds resets node count & time,
4044        which looks bad)) */
4045     programStats.ok_to_send = 0;
4046 }
4047
4048 void ics_update_width(new_width)
4049         int new_width;
4050 {
4051         ics_printf("set width %d\n", new_width);
4052 }
4053
4054 void
4055 SendMoveToProgram(moveNum, cps)
4056      int moveNum;
4057      ChessProgramState *cps;
4058 {
4059     char buf[MSG_SIZ];
4060
4061     if (cps->useUsermove) {
4062       SendToProgram("usermove ", cps);
4063     }
4064     if (cps->useSAN) {
4065       char *space;
4066       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4067         int len = space - parseList[moveNum];
4068         memcpy(buf, parseList[moveNum], len);
4069         buf[len++] = '\n';
4070         buf[len] = NULLCHAR;
4071       } else {
4072         sprintf(buf, "%s\n", parseList[moveNum]);
4073       }
4074       SendToProgram(buf, cps);
4075     } else {
4076       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4077         AlphaRank(moveList[moveNum], 4);
4078         SendToProgram(moveList[moveNum], cps);
4079         AlphaRank(moveList[moveNum], 4); // and back
4080       } else
4081       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4082        * the engine. It would be nice to have a better way to identify castle
4083        * moves here. */
4084       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4085                                                                          && cps->useOOCastle) {
4086         int fromX = moveList[moveNum][0] - AAA;
4087         int fromY = moveList[moveNum][1] - ONE;
4088         int toX = moveList[moveNum][2] - AAA;
4089         int toY = moveList[moveNum][3] - ONE;
4090         if((boards[moveNum][fromY][fromX] == WhiteKing
4091             && boards[moveNum][toY][toX] == WhiteRook)
4092            || (boards[moveNum][fromY][fromX] == BlackKing
4093                && boards[moveNum][toY][toX] == BlackRook)) {
4094           if(toX > fromX) SendToProgram("O-O\n", cps);
4095           else SendToProgram("O-O-O\n", cps);
4096         }
4097         else SendToProgram(moveList[moveNum], cps);
4098       }
4099       else SendToProgram(moveList[moveNum], cps);
4100       /* End of additions by Tord */
4101     }
4102
4103     /* [HGM] setting up the opening has brought engine in force mode! */
4104     /*       Send 'go' if we are in a mode where machine should play. */
4105     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4106         (gameMode == TwoMachinesPlay   ||
4107 #ifdef ZIPPY
4108          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4109 #endif
4110          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4111         SendToProgram("go\n", cps);
4112   if (appData.debugMode) {
4113     fprintf(debugFP, "(extra)\n");
4114   }
4115     }
4116     setboardSpoiledMachineBlack = 0;
4117 }
4118
4119 void
4120 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4121      ChessMove moveType;
4122      int fromX, fromY, toX, toY;
4123 {
4124     char user_move[MSG_SIZ];
4125
4126     switch (moveType) {
4127       default:
4128         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4129                 (int)moveType, fromX, fromY, toX, toY);
4130         DisplayError(user_move + strlen("say "), 0);
4131         break;
4132       case WhiteKingSideCastle:
4133       case BlackKingSideCastle:
4134       case WhiteQueenSideCastleWild:
4135       case BlackQueenSideCastleWild:
4136       /* PUSH Fabien */
4137       case WhiteHSideCastleFR:
4138       case BlackHSideCastleFR:
4139       /* POP Fabien */
4140         sprintf(user_move, "o-o\n");
4141         break;
4142       case WhiteQueenSideCastle:
4143       case BlackQueenSideCastle:
4144       case WhiteKingSideCastleWild:
4145       case BlackKingSideCastleWild:
4146       /* PUSH Fabien */
4147       case WhiteASideCastleFR:
4148       case BlackASideCastleFR:
4149       /* POP Fabien */
4150         sprintf(user_move, "o-o-o\n");
4151         break;
4152       case WhitePromotionQueen:
4153       case BlackPromotionQueen:
4154       case WhitePromotionRook:
4155       case BlackPromotionRook:
4156       case WhitePromotionBishop:
4157       case BlackPromotionBishop:
4158       case WhitePromotionKnight:
4159       case BlackPromotionKnight:
4160       case WhitePromotionKing:
4161       case BlackPromotionKing:
4162       case WhitePromotionChancellor:
4163       case BlackPromotionChancellor:
4164       case WhitePromotionArchbishop:
4165       case BlackPromotionArchbishop:
4166         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4167             sprintf(user_move, "%c%c%c%c=%c\n",
4168                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4169                 PieceToChar(WhiteFerz));
4170         else if(gameInfo.variant == VariantGreat)
4171             sprintf(user_move, "%c%c%c%c=%c\n",
4172                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173                 PieceToChar(WhiteMan));
4174         else
4175             sprintf(user_move, "%c%c%c%c=%c\n",
4176                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177                 PieceToChar(PromoPiece(moveType)));
4178         break;
4179       case WhiteDrop:
4180       case BlackDrop:
4181         sprintf(user_move, "%c@%c%c\n",
4182                 ToUpper(PieceToChar((ChessSquare) fromX)),
4183                 AAA + toX, ONE + toY);
4184         break;
4185       case NormalMove:
4186       case WhiteCapturesEnPassant:
4187       case BlackCapturesEnPassant:
4188       case IllegalMove:  /* could be a variant we don't quite understand */
4189         sprintf(user_move, "%c%c%c%c\n",
4190                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4191         break;
4192     }
4193     SendToICS(user_move);
4194     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4195         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4196 }
4197
4198 void
4199 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4200      int rf, ff, rt, ft;
4201      char promoChar;
4202      char move[7];
4203 {
4204     if (rf == DROP_RANK) {
4205         sprintf(move, "%c@%c%c\n",
4206                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4207     } else {
4208         if (promoChar == 'x' || promoChar == NULLCHAR) {
4209             sprintf(move, "%c%c%c%c\n",
4210                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4211         } else {
4212             sprintf(move, "%c%c%c%c%c\n",
4213                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4214         }
4215     }
4216 }
4217
4218 void
4219 ProcessICSInitScript(f)
4220      FILE *f;
4221 {
4222     char buf[MSG_SIZ];
4223
4224     while (fgets(buf, MSG_SIZ, f)) {
4225         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4226     }
4227
4228     fclose(f);
4229 }
4230
4231
4232 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4233 void
4234 AlphaRank(char *move, int n)
4235 {
4236 //    char *p = move, c; int x, y;
4237
4238     if (appData.debugMode) {
4239         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4240     }
4241
4242     if(move[1]=='*' &&
4243        move[2]>='0' && move[2]<='9' &&
4244        move[3]>='a' && move[3]<='x'    ) {
4245         move[1] = '@';
4246         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4247         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4248     } else
4249     if(move[0]>='0' && move[0]<='9' &&
4250        move[1]>='a' && move[1]<='x' &&
4251        move[2]>='0' && move[2]<='9' &&
4252        move[3]>='a' && move[3]<='x'    ) {
4253         /* input move, Shogi -> normal */
4254         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4255         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4256         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4257         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4258     } else
4259     if(move[1]=='@' &&
4260        move[3]>='0' && move[3]<='9' &&
4261        move[2]>='a' && move[2]<='x'    ) {
4262         move[1] = '*';
4263         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4264         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4265     } else
4266     if(
4267        move[0]>='a' && move[0]<='x' &&
4268        move[3]>='0' && move[3]<='9' &&
4269        move[2]>='a' && move[2]<='x'    ) {
4270          /* output move, normal -> Shogi */
4271         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4272         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4273         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4274         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4275         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4276     }
4277     if (appData.debugMode) {
4278         fprintf(debugFP, "   out = '%s'\n", move);
4279     }
4280 }
4281
4282 /* Parser for moves from gnuchess, ICS, or user typein box */
4283 Boolean
4284 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4285      char *move;
4286      int moveNum;
4287      ChessMove *moveType;
4288      int *fromX, *fromY, *toX, *toY;
4289      char *promoChar;
4290 {
4291     if (appData.debugMode) {
4292         fprintf(debugFP, "move to parse: %s\n", move);
4293     }
4294     *moveType = yylexstr(moveNum, move);
4295
4296     switch (*moveType) {
4297       case WhitePromotionChancellor:
4298       case BlackPromotionChancellor:
4299       case WhitePromotionArchbishop:
4300       case BlackPromotionArchbishop:
4301       case WhitePromotionQueen:
4302       case BlackPromotionQueen:
4303       case WhitePromotionRook:
4304       case BlackPromotionRook:
4305       case WhitePromotionBishop:
4306       case BlackPromotionBishop:
4307       case WhitePromotionKnight:
4308       case BlackPromotionKnight:
4309       case WhitePromotionKing:
4310       case BlackPromotionKing:
4311       case NormalMove:
4312       case WhiteCapturesEnPassant:
4313       case BlackCapturesEnPassant:
4314       case WhiteKingSideCastle:
4315       case WhiteQueenSideCastle:
4316       case BlackKingSideCastle:
4317       case BlackQueenSideCastle:
4318       case WhiteKingSideCastleWild:
4319       case WhiteQueenSideCastleWild:
4320       case BlackKingSideCastleWild:
4321       case BlackQueenSideCastleWild:
4322       /* Code added by Tord: */
4323       case WhiteHSideCastleFR:
4324       case WhiteASideCastleFR:
4325       case BlackHSideCastleFR:
4326       case BlackASideCastleFR:
4327       /* End of code added by Tord */
4328       case IllegalMove:         /* bug or odd chess variant */
4329         *fromX = currentMoveString[0] - AAA;
4330         *fromY = currentMoveString[1] - ONE;
4331         *toX = currentMoveString[2] - AAA;
4332         *toY = currentMoveString[3] - ONE;
4333         *promoChar = currentMoveString[4];
4334         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4335             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4336     if (appData.debugMode) {
4337         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4338     }
4339             *fromX = *fromY = *toX = *toY = 0;
4340             return FALSE;
4341         }
4342         if (appData.testLegality) {
4343           return (*moveType != IllegalMove);
4344         } else {
4345           return !(fromX == fromY && toX == toY);
4346         }
4347
4348       case WhiteDrop:
4349       case BlackDrop:
4350         *fromX = *moveType == WhiteDrop ?
4351           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4352           (int) CharToPiece(ToLower(currentMoveString[0]));
4353         *fromY = DROP_RANK;
4354         *toX = currentMoveString[2] - AAA;
4355         *toY = currentMoveString[3] - ONE;
4356         *promoChar = NULLCHAR;
4357         return TRUE;
4358
4359       case AmbiguousMove:
4360       case ImpossibleMove:
4361       case (ChessMove) 0:       /* end of file */
4362       case ElapsedTime:
4363       case Comment:
4364       case PGNTag:
4365       case NAG:
4366       case WhiteWins:
4367       case BlackWins:
4368       case GameIsDrawn:
4369       default:
4370     if (appData.debugMode) {
4371         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4372     }
4373         /* bug? */
4374         *fromX = *fromY = *toX = *toY = 0;
4375         *promoChar = NULLCHAR;
4376         return FALSE;
4377     }
4378 }
4379
4380 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4381 // All positions will have equal probability, but the current method will not provide a unique
4382 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4383 #define DARK 1
4384 #define LITE 2
4385 #define ANY 3
4386
4387 int squaresLeft[4];
4388 int piecesLeft[(int)BlackPawn];
4389 int seed, nrOfShuffles;
4390
4391 void GetPositionNumber()
4392 {       // sets global variable seed
4393         int i;
4394
4395         seed = appData.defaultFrcPosition;
4396         if(seed < 0) { // randomize based on time for negative FRC position numbers
4397                 for(i=0; i<50; i++) seed += random();
4398                 seed = random() ^ random() >> 8 ^ random() << 8;
4399                 if(seed<0) seed = -seed;
4400         }
4401 }
4402
4403 int put(Board board, int pieceType, int rank, int n, int shade)
4404 // put the piece on the (n-1)-th empty squares of the given shade
4405 {
4406         int i;
4407
4408         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4409                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4410                         board[rank][i] = (ChessSquare) pieceType;
4411                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4412                         squaresLeft[ANY]--;
4413                         piecesLeft[pieceType]--;
4414                         return i;
4415                 }
4416         }
4417         return -1;
4418 }
4419
4420
4421 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4422 // calculate where the next piece goes, (any empty square), and put it there
4423 {
4424         int i;
4425
4426         i = seed % squaresLeft[shade];
4427         nrOfShuffles *= squaresLeft[shade];
4428         seed /= squaresLeft[shade];
4429         put(board, pieceType, rank, i, shade);
4430 }
4431
4432 void AddTwoPieces(Board board, int pieceType, int rank)
4433 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4434 {
4435         int i, n=squaresLeft[ANY], j=n-1, k;
4436
4437         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4438         i = seed % k;  // pick one
4439         nrOfShuffles *= k;
4440         seed /= k;
4441         while(i >= j) i -= j--;
4442         j = n - 1 - j; i += j;
4443         put(board, pieceType, rank, j, ANY);
4444         put(board, pieceType, rank, i, ANY);
4445 }
4446
4447 void SetUpShuffle(Board board, int number)
4448 {
4449         int i, p, first=1;
4450
4451         GetPositionNumber(); nrOfShuffles = 1;
4452
4453         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4454         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4455         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4456
4457         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4458
4459         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4460             p = (int) board[0][i];
4461             if(p < (int) BlackPawn) piecesLeft[p] ++;
4462             board[0][i] = EmptySquare;
4463         }
4464
4465         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4466             // shuffles restricted to allow normal castling put KRR first
4467             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4468                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4469             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4470                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4471             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4472                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4473             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4474                 put(board, WhiteRook, 0, 0, ANY);
4475             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4476         }
4477
4478         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4479             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4480             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4481                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4482                 while(piecesLeft[p] >= 2) {
4483                     AddOnePiece(board, p, 0, LITE);
4484                     AddOnePiece(board, p, 0, DARK);
4485                 }
4486                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4487             }
4488
4489         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4490             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4491             // but we leave King and Rooks for last, to possibly obey FRC restriction
4492             if(p == (int)WhiteRook) continue;
4493             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4494             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4495         }
4496
4497         // now everything is placed, except perhaps King (Unicorn) and Rooks
4498
4499         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4500             // Last King gets castling rights
4501             while(piecesLeft[(int)WhiteUnicorn]) {
4502                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4503                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4504             }
4505
4506             while(piecesLeft[(int)WhiteKing]) {
4507                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4508                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4509             }
4510
4511
4512         } else {
4513             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4514             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4515         }
4516
4517         // Only Rooks can be left; simply place them all
4518         while(piecesLeft[(int)WhiteRook]) {
4519                 i = put(board, WhiteRook, 0, 0, ANY);
4520                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4521                         if(first) {
4522                                 first=0;
4523                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4524                         }
4525                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4526                 }
4527         }
4528         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4529             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4530         }
4531
4532         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4533 }
4534
4535 int SetCharTable( char *table, const char * map )
4536 /* [HGM] moved here from winboard.c because of its general usefulness */
4537 /*       Basically a safe strcpy that uses the last character as King */
4538 {
4539     int result = FALSE; int NrPieces;
4540
4541     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4542                     && NrPieces >= 12 && !(NrPieces&1)) {
4543         int i; /* [HGM] Accept even length from 12 to 34 */
4544
4545         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4546         for( i=0; i<NrPieces/2-1; i++ ) {
4547             table[i] = map[i];
4548             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4549         }
4550         table[(int) WhiteKing]  = map[NrPieces/2-1];
4551         table[(int) BlackKing]  = map[NrPieces-1];
4552
4553         result = TRUE;
4554     }
4555
4556     return result;
4557 }
4558
4559 void Prelude(Board board)
4560 {       // [HGM] superchess: random selection of exo-pieces
4561         int i, j, k; ChessSquare p;
4562         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4563
4564         GetPositionNumber(); // use FRC position number
4565
4566         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4567             SetCharTable(pieceToChar, appData.pieceToCharTable);
4568             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4569                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4570         }
4571
4572         j = seed%4;                 seed /= 4;
4573         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4574         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4575         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4576         j = seed%3 + (seed%3 >= j); seed /= 3;
4577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4580         j = seed%3;                 seed /= 3;
4581         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4584         j = seed%2 + (seed%2 >= j); seed /= 2;
4585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4588         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4589         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4590         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4591         put(board, exoPieces[0],    0, 0, ANY);
4592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4593 }
4594
4595 void
4596 InitPosition(redraw)
4597      int redraw;
4598 {
4599     ChessSquare (* pieces)[BOARD_SIZE];
4600     int i, j, pawnRow, overrule,
4601     oldx = gameInfo.boardWidth,
4602     oldy = gameInfo.boardHeight,
4603     oldh = gameInfo.holdingsWidth,
4604     oldv = gameInfo.variant;
4605
4606     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4607
4608     /* [AS] Initialize pv info list [HGM] and game status */
4609     {
4610         for( i=0; i<MAX_MOVES; i++ ) {
4611             pvInfoList[i].depth = 0;
4612             epStatus[i]=EP_NONE;
4613             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4614         }
4615
4616         initialRulePlies = 0; /* 50-move counter start */
4617
4618         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4619         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4620     }
4621
4622
4623     /* [HGM] logic here is completely changed. In stead of full positions */
4624     /* the initialized data only consist of the two backranks. The switch */
4625     /* selects which one we will use, which is than copied to the Board   */
4626     /* initialPosition, which for the rest is initialized by Pawns and    */
4627     /* empty squares. This initial position is then copied to boards[0],  */
4628     /* possibly after shuffling, so that it remains available.            */
4629
4630     gameInfo.holdingsWidth = 0; /* default board sizes */
4631     gameInfo.boardWidth    = 8;
4632     gameInfo.boardHeight   = 8;
4633     gameInfo.holdingsSize  = 0;
4634     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4635     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4636     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4637
4638     switch (gameInfo.variant) {
4639     case VariantFischeRandom:
4640       shuffleOpenings = TRUE;
4641     default:
4642       pieces = FIDEArray;
4643       break;
4644     case VariantShatranj:
4645       pieces = ShatranjArray;
4646       nrCastlingRights = 0;
4647       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4648       break;
4649     case VariantTwoKings:
4650       pieces = twoKingsArray;
4651       break;
4652     case VariantCapaRandom:
4653       shuffleOpenings = TRUE;
4654     case VariantCapablanca:
4655       pieces = CapablancaArray;
4656       gameInfo.boardWidth = 10;
4657       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4658       break;
4659     case VariantGothic:
4660       pieces = GothicArray;
4661       gameInfo.boardWidth = 10;
4662       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4663       break;
4664     case VariantJanus:
4665       pieces = JanusArray;
4666       gameInfo.boardWidth = 10;
4667       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4668       nrCastlingRights = 6;
4669         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4670         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4671         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4672         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4673         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4674         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4675       break;
4676     case VariantFalcon:
4677       pieces = FalconArray;
4678       gameInfo.boardWidth = 10;
4679       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4680       break;
4681     case VariantXiangqi:
4682       pieces = XiangqiArray;
4683       gameInfo.boardWidth  = 9;
4684       gameInfo.boardHeight = 10;
4685       nrCastlingRights = 0;
4686       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4687       break;
4688     case VariantShogi:
4689       pieces = ShogiArray;
4690       gameInfo.boardWidth  = 9;
4691       gameInfo.boardHeight = 9;
4692       gameInfo.holdingsSize = 7;
4693       nrCastlingRights = 0;
4694       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4695       break;
4696     case VariantCourier:
4697       pieces = CourierArray;
4698       gameInfo.boardWidth  = 12;
4699       nrCastlingRights = 0;
4700       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4701       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4702       break;
4703     case VariantKnightmate:
4704       pieces = KnightmateArray;
4705       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4706       break;
4707     case VariantFairy:
4708       pieces = fairyArray;
4709       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4710       break;
4711     case VariantGreat:
4712       pieces = GreatArray;
4713       gameInfo.boardWidth = 10;
4714       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4715       gameInfo.holdingsSize = 8;
4716       break;
4717     case VariantSuper:
4718       pieces = FIDEArray;
4719       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4720       gameInfo.holdingsSize = 8;
4721       startedFromSetupPosition = TRUE;
4722       break;
4723     case VariantCrazyhouse:
4724     case VariantBughouse:
4725       pieces = FIDEArray;
4726       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4727       gameInfo.holdingsSize = 5;
4728       break;
4729     case VariantWildCastle:
4730       pieces = FIDEArray;
4731       /* !!?shuffle with kings guaranteed to be on d or e file */
4732       shuffleOpenings = 1;
4733       break;
4734     case VariantNoCastle:
4735       pieces = FIDEArray;
4736       nrCastlingRights = 0;
4737       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4738       /* !!?unconstrained back-rank shuffle */
4739       shuffleOpenings = 1;
4740       break;
4741     }
4742
4743     overrule = 0;
4744     if(appData.NrFiles >= 0) {
4745         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4746         gameInfo.boardWidth = appData.NrFiles;
4747     }
4748     if(appData.NrRanks >= 0) {
4749         gameInfo.boardHeight = appData.NrRanks;
4750     }
4751     if(appData.holdingsSize >= 0) {
4752         i = appData.holdingsSize;
4753         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4754         gameInfo.holdingsSize = i;
4755     }
4756     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4757     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4758         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4759
4760     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4761     if(pawnRow < 1) pawnRow = 1;
4762
4763     /* User pieceToChar list overrules defaults */
4764     if(appData.pieceToCharTable != NULL)
4765         SetCharTable(pieceToChar, appData.pieceToCharTable);
4766
4767     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4768
4769         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4770             s = (ChessSquare) 0; /* account holding counts in guard band */
4771         for( i=0; i<BOARD_HEIGHT; i++ )
4772             initialPosition[i][j] = s;
4773
4774         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4775         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4776         initialPosition[pawnRow][j] = WhitePawn;
4777         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4778         if(gameInfo.variant == VariantXiangqi) {
4779             if(j&1) {
4780                 initialPosition[pawnRow][j] =
4781                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4782                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4783                    initialPosition[2][j] = WhiteCannon;
4784                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4785                 }
4786             }
4787         }
4788         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4789     }
4790     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4791
4792             j=BOARD_LEFT+1;
4793             initialPosition[1][j] = WhiteBishop;
4794             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4795             j=BOARD_RGHT-2;
4796             initialPosition[1][j] = WhiteRook;
4797             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4798     }
4799
4800     if( nrCastlingRights == -1) {
4801         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4802         /*       This sets default castling rights from none to normal corners   */
4803         /* Variants with other castling rights must set them themselves above    */
4804         nrCastlingRights = 6;
4805
4806         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4807         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4808         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4809         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4810         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4811         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4812      }
4813
4814      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4815      if(gameInfo.variant == VariantGreat) { // promotion commoners
4816         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4817         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4818         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4819         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4820      }
4821   if (appData.debugMode) {
4822     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4823   }
4824     if(shuffleOpenings) {
4825         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4826         startedFromSetupPosition = TRUE;
4827     }
4828     if(startedFromPositionFile) {
4829       /* [HGM] loadPos: use PositionFile for every new game */
4830       CopyBoard(initialPosition, filePosition);
4831       for(i=0; i<nrCastlingRights; i++)
4832           castlingRights[0][i] = initialRights[i] = fileRights[i];
4833       startedFromSetupPosition = TRUE;
4834     }
4835
4836     CopyBoard(boards[0], initialPosition);
4837     if(oldx != gameInfo.boardWidth ||
4838        oldy != gameInfo.boardHeight ||
4839        oldh != gameInfo.holdingsWidth
4840 #ifdef GOTHIC
4841        || oldv == VariantGothic ||        // For licensing popups
4842        gameInfo.variant == VariantGothic
4843 #endif
4844 #ifdef FALCON
4845        || oldv == VariantFalcon ||
4846        gameInfo.variant == VariantFalcon
4847 #endif
4848                                          )
4849       {
4850             InitDrawingSizes(-2 ,0);
4851       }
4852
4853     if (redraw)
4854       DrawPosition(TRUE, boards[currentMove]);
4855
4856 }
4857
4858 void
4859 SendBoard(cps, moveNum)
4860      ChessProgramState *cps;
4861      int moveNum;
4862 {
4863     char message[MSG_SIZ];
4864
4865     if (cps->useSetboard) {
4866       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4867       sprintf(message, "setboard %s\n", fen);
4868       SendToProgram(message, cps);
4869       free(fen);
4870
4871     } else {
4872       ChessSquare *bp;
4873       int i, j;
4874       /* Kludge to set black to move, avoiding the troublesome and now
4875        * deprecated "black" command.
4876        */
4877       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4878
4879       SendToProgram("edit\n", cps);
4880       SendToProgram("#\n", cps);
4881       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4882         bp = &boards[moveNum][i][BOARD_LEFT];
4883         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4884           if ((int) *bp < (int) BlackPawn) {
4885             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4886                     AAA + j, ONE + i);
4887             if(message[0] == '+' || message[0] == '~') {
4888                 sprintf(message, "%c%c%c+\n",
4889                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4890                         AAA + j, ONE + i);
4891             }
4892             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4893                 message[1] = BOARD_RGHT   - 1 - j + '1';
4894                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4895             }
4896             SendToProgram(message, cps);
4897           }
4898         }
4899       }
4900
4901       SendToProgram("c\n", cps);
4902       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4903         bp = &boards[moveNum][i][BOARD_LEFT];
4904         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4905           if (((int) *bp != (int) EmptySquare)
4906               && ((int) *bp >= (int) BlackPawn)) {
4907             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4908                     AAA + j, ONE + i);
4909             if(message[0] == '+' || message[0] == '~') {
4910                 sprintf(message, "%c%c%c+\n",
4911                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4912                         AAA + j, ONE + i);
4913             }
4914             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4915                 message[1] = BOARD_RGHT   - 1 - j + '1';
4916                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4917             }
4918             SendToProgram(message, cps);
4919           }
4920         }
4921       }
4922
4923       SendToProgram(".\n", cps);
4924     }
4925     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4926 }
4927
4928 int
4929 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4930 {
4931     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4932     /* [HGM] add Shogi promotions */
4933     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4934     ChessSquare piece;
4935     ChessMove moveType;
4936     Boolean premove;
4937
4938     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4939     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4940
4941     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4942       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4943         return FALSE;
4944
4945     piece = boards[currentMove][fromY][fromX];
4946     if(gameInfo.variant == VariantShogi) {
4947         promotionZoneSize = 3;
4948         highestPromotingPiece = (int)WhiteFerz;
4949     }
4950
4951     // next weed out all moves that do not touch the promotion zone at all
4952     if((int)piece >= BlackPawn) {
4953         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4954              return FALSE;
4955         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4956     } else {
4957         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4958            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4959     }
4960
4961     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4962
4963     // weed out mandatory Shogi promotions
4964     if(gameInfo.variant == VariantShogi) {
4965         if(piece >= BlackPawn) {
4966             if(toY == 0 && piece == BlackPawn ||
4967                toY == 0 && piece == BlackQueen ||
4968                toY <= 1 && piece == BlackKnight) {
4969                 *promoChoice = '+';
4970                 return FALSE;
4971             }
4972         } else {
4973             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4974                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4975                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4976                 *promoChoice = '+';
4977                 return FALSE;
4978             }
4979         }
4980     }
4981
4982     // weed out obviously illegal Pawn moves
4983     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4984         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4985         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4986         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4987         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4988         // note we are not allowed to test for valid (non-)capture, due to premove
4989     }
4990
4991     // we either have a choice what to promote to, or (in Shogi) whether to promote
4992     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4993         *promoChoice = PieceToChar(BlackFerz);  // no choice
4994         return FALSE;
4995     }
4996     if(appData.alwaysPromoteToQueen) { // predetermined
4997         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4998              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4999         else *promoChoice = PieceToChar(BlackQueen);
5000         return FALSE;
5001     }
5002
5003     // suppress promotion popup on illegal moves that are not premoves
5004     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5005               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5006     if(appData.testLegality && !premove) {
5007         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5008                         epStatus[currentMove], castlingRights[currentMove],
5009                         fromY, fromX, toY, toX, NULLCHAR);
5010         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5011            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5012             return FALSE;
5013     }
5014
5015     return TRUE;
5016 }
5017
5018 int
5019 InPalace(row, column)
5020      int row, column;
5021 {   /* [HGM] for Xiangqi */
5022     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5023          column < (BOARD_WIDTH + 4)/2 &&
5024          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5025     return FALSE;
5026 }
5027
5028 int
5029 PieceForSquare (x, y)
5030      int x;
5031      int y;
5032 {
5033   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5034      return -1;
5035   else
5036      return boards[currentMove][y][x];
5037 }
5038
5039 int
5040 OKToStartUserMove(x, y)
5041      int x, y;
5042 {
5043     ChessSquare from_piece;
5044     int white_piece;
5045
5046     if (matchMode) return FALSE;
5047     if (gameMode == EditPosition) return TRUE;
5048
5049     if (x >= 0 && y >= 0)
5050       from_piece = boards[currentMove][y][x];
5051     else
5052       from_piece = EmptySquare;
5053
5054     if (from_piece == EmptySquare) return FALSE;
5055
5056     white_piece = (int)from_piece >= (int)WhitePawn &&
5057       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5058
5059     switch (gameMode) {
5060       case PlayFromGameFile:
5061       case AnalyzeFile:
5062       case TwoMachinesPlay:
5063       case EndOfGame:
5064         return FALSE;
5065
5066       case IcsObserving:
5067       case IcsIdle:
5068         return FALSE;
5069
5070       case MachinePlaysWhite:
5071       case IcsPlayingBlack:
5072         if (appData.zippyPlay) return FALSE;
5073         if (white_piece) {
5074             DisplayMoveError(_("You are playing Black"));
5075             return FALSE;
5076         }
5077         break;
5078
5079       case MachinePlaysBlack:
5080       case IcsPlayingWhite:
5081         if (appData.zippyPlay) return FALSE;
5082         if (!white_piece) {
5083             DisplayMoveError(_("You are playing White"));
5084             return FALSE;
5085         }
5086         break;
5087
5088       case EditGame:
5089         if (!white_piece && WhiteOnMove(currentMove)) {
5090             DisplayMoveError(_("It is White's turn"));
5091             return FALSE;
5092         }
5093         if (white_piece && !WhiteOnMove(currentMove)) {
5094             DisplayMoveError(_("It is Black's turn"));
5095             return FALSE;
5096         }
5097         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5098             /* Editing correspondence game history */
5099             /* Could disallow this or prompt for confirmation */
5100             cmailOldMove = -1;
5101         }
5102         if (currentMove < forwardMostMove) {
5103             /* Discarding moves */
5104             /* Could prompt for confirmation here,
5105                but I don't think that's such a good idea */
5106             forwardMostMove = currentMove;
5107         }
5108         break;
5109
5110       case BeginningOfGame:
5111         if (appData.icsActive) return FALSE;
5112         if (!appData.noChessProgram) {
5113             if (!white_piece) {
5114                 DisplayMoveError(_("You are playing White"));
5115                 return FALSE;
5116             }
5117         }
5118         break;
5119
5120       case Training:
5121         if (!white_piece && WhiteOnMove(currentMove)) {
5122             DisplayMoveError(_("It is White's turn"));
5123             return FALSE;
5124         }
5125         if (white_piece && !WhiteOnMove(currentMove)) {
5126             DisplayMoveError(_("It is Black's turn"));
5127             return FALSE;
5128         }
5129         break;
5130
5131       default:
5132       case IcsExamining:
5133         break;
5134     }
5135     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5136         && gameMode != AnalyzeFile && gameMode != Training) {
5137         DisplayMoveError(_("Displayed position is not current"));
5138         return FALSE;
5139     }
5140     return TRUE;
5141 }
5142
5143 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5144 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5145 int lastLoadGameUseList = FALSE;
5146 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5147 ChessMove lastLoadGameStart = (ChessMove) 0;
5148
5149 ChessMove
5150 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5151      int fromX, fromY, toX, toY;
5152      int promoChar;
5153      Boolean captureOwn;
5154 {
5155     ChessMove moveType;
5156     ChessSquare pdown, pup;
5157
5158     /* Check if the user is playing in turn.  This is complicated because we
5159        let the user "pick up" a piece before it is his turn.  So the piece he
5160        tried to pick up may have been captured by the time he puts it down!
5161        Therefore we use the color the user is supposed to be playing in this
5162        test, not the color of the piece that is currently on the starting
5163        square---except in EditGame mode, where the user is playing both
5164        sides; fortunately there the capture race can't happen.  (It can
5165        now happen in IcsExamining mode, but that's just too bad.  The user
5166        will get a somewhat confusing message in that case.)
5167        */
5168
5169     switch (gameMode) {
5170       case PlayFromGameFile:
5171       case AnalyzeFile:
5172       case TwoMachinesPlay:
5173       case EndOfGame:
5174       case IcsObserving:
5175       case IcsIdle:
5176         /* We switched into a game mode where moves are not accepted,
5177            perhaps while the mouse button was down. */
5178         return ImpossibleMove;
5179
5180       case MachinePlaysWhite:
5181         /* User is moving for Black */
5182         if (WhiteOnMove(currentMove)) {
5183             DisplayMoveError(_("It is White's turn"));
5184             return ImpossibleMove;
5185         }
5186         break;
5187
5188       case MachinePlaysBlack:
5189         /* User is moving for White */
5190         if (!WhiteOnMove(currentMove)) {
5191             DisplayMoveError(_("It is Black's turn"));
5192             return ImpossibleMove;
5193         }
5194         break;
5195
5196       case EditGame:
5197       case IcsExamining:
5198       case BeginningOfGame:
5199       case AnalyzeMode:
5200       case Training:
5201         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5202             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5203             /* User is moving for Black */
5204             if (WhiteOnMove(currentMove)) {
5205                 DisplayMoveError(_("It is White's turn"));
5206                 return ImpossibleMove;
5207             }
5208         } else {
5209             /* User is moving for White */
5210             if (!WhiteOnMove(currentMove)) {
5211                 DisplayMoveError(_("It is Black's turn"));
5212                 return ImpossibleMove;
5213             }
5214         }
5215         break;
5216
5217       case IcsPlayingBlack:
5218         /* User is moving for Black */
5219         if (WhiteOnMove(currentMove)) {
5220             if (!appData.premove) {
5221                 DisplayMoveError(_("It is White's turn"));
5222             } else if (toX >= 0 && toY >= 0) {
5223                 premoveToX = toX;
5224                 premoveToY = toY;
5225                 premoveFromX = fromX;
5226                 premoveFromY = fromY;
5227                 premovePromoChar = promoChar;
5228                 gotPremove = 1;
5229                 if (appData.debugMode)
5230                     fprintf(debugFP, "Got premove: fromX %d,"
5231                             "fromY %d, toX %d, toY %d\n",
5232                             fromX, fromY, toX, toY);
5233             }
5234             return ImpossibleMove;
5235         }
5236         break;
5237
5238       case IcsPlayingWhite:
5239         /* User is moving for White */
5240         if (!WhiteOnMove(currentMove)) {
5241             if (!appData.premove) {
5242                 DisplayMoveError(_("It is Black's turn"));
5243             } else if (toX >= 0 && toY >= 0) {
5244                 premoveToX = toX;
5245                 premoveToY = toY;
5246                 premoveFromX = fromX;
5247                 premoveFromY = fromY;
5248                 premovePromoChar = promoChar;
5249                 gotPremove = 1;
5250                 if (appData.debugMode)
5251                     fprintf(debugFP, "Got premove: fromX %d,"
5252                             "fromY %d, toX %d, toY %d\n",
5253                             fromX, fromY, toX, toY);
5254             }
5255             return ImpossibleMove;
5256         }
5257         break;
5258
5259       default:
5260         break;
5261
5262       case EditPosition:
5263         /* EditPosition, empty square, or different color piece;
5264            click-click move is possible */
5265         if (toX == -2 || toY == -2) {
5266             boards[0][fromY][fromX] = EmptySquare;
5267             return AmbiguousMove;
5268         } else if (toX >= 0 && toY >= 0) {
5269             boards[0][toY][toX] = boards[0][fromY][fromX];
5270             boards[0][fromY][fromX] = EmptySquare;
5271             return AmbiguousMove;
5272         }
5273         return ImpossibleMove;
5274     }
5275
5276     if(toX < 0 || toY < 0) return ImpossibleMove;
5277     pdown = boards[currentMove][fromY][fromX];
5278     pup = boards[currentMove][toY][toX];
5279
5280     /* [HGM] If move started in holdings, it means a drop */
5281     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5282          if( pup != EmptySquare ) return ImpossibleMove;
5283          if(appData.testLegality) {
5284              /* it would be more logical if LegalityTest() also figured out
5285               * which drops are legal. For now we forbid pawns on back rank.
5286               * Shogi is on its own here...
5287               */
5288              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5289                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5290                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5291          }
5292          return WhiteDrop; /* Not needed to specify white or black yet */
5293     }
5294
5295     userOfferedDraw = FALSE;
5296
5297     /* [HGM] always test for legality, to get promotion info */
5298     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5299                           epStatus[currentMove], castlingRights[currentMove],
5300                                          fromY, fromX, toY, toX, promoChar);
5301     /* [HGM] but possibly ignore an IllegalMove result */
5302     if (appData.testLegality) {
5303         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5304             DisplayMoveError(_("Illegal move"));
5305             return ImpossibleMove;
5306         }
5307     }
5308     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5309     return moveType;
5310     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5311        function is made into one that returns an OK move type if FinishMove
5312        should be called. This to give the calling driver routine the
5313        opportunity to finish the userMove input with a promotion popup,
5314        without bothering the user with this for invalid or illegal moves */
5315
5316 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5317 }
5318
5319 /* Common tail of UserMoveEvent and DropMenuEvent */
5320 int
5321 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5322      ChessMove moveType;
5323      int fromX, fromY, toX, toY;
5324      /*char*/int promoChar;
5325 {
5326   char *bookHit = 0;
5327
5328   if(appData.debugMode)
5329     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5330
5331   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5332     {
5333       // [HGM] superchess: suppress promotions to non-available piece
5334       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5335       if(WhiteOnMove(currentMove))
5336         {
5337           if(!boards[currentMove][k][BOARD_WIDTH-2])
5338             return 0;
5339         }
5340       else
5341         {
5342           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5343             return 0;
5344         }
5345     }
5346   
5347   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348      move type in caller when we know the move is a legal promotion */
5349   if(moveType == NormalMove && promoChar)
5350     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5351
5352   if(appData.debugMode) 
5353     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5354
5355   /* [HGM] convert drag-and-drop piece drops to standard form */
5356   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5357     {
5358       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5359       if(appData.debugMode) 
5360         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5361                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5362       //         fromX = boards[currentMove][fromY][fromX];
5363       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5364       if(fromX == 0) 
5365         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5366
5367       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5368
5369       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5370         fromX++; 
5371
5372       fromY = DROP_RANK;
5373     }
5374
5375   /* [HGM] <popupFix> The following if has been moved here from
5376      UserMoveEvent(). Because it seemed to belon here (why not allow
5377      piece drops in training games?), and because it can only be
5378      performed after it is known to what we promote. */
5379   if (gameMode == Training)
5380     {
5381       /* compare the move played on the board to the next move in the
5382        * game. If they match, display the move and the opponent's response.
5383        * If they don't match, display an error message.
5384        */
5385       int saveAnimate;
5386       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5387       CopyBoard(testBoard, boards[currentMove]);
5388       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5389
5390       if (CompareBoards(testBoard, boards[currentMove+1]))
5391         {
5392           ForwardInner(currentMove+1);
5393
5394           /* Autoplay the opponent's response.
5395            * if appData.animate was TRUE when Training mode was entered,
5396            * the response will be animated.
5397            */
5398           saveAnimate = appData.animate;
5399           appData.animate = animateTraining;
5400           ForwardInner(currentMove+1);
5401           appData.animate = saveAnimate;
5402
5403           /* check for the end of the game */
5404           if (currentMove >= forwardMostMove)
5405             {
5406               gameMode = PlayFromGameFile;
5407               ModeHighlight();
5408               SetTrainingModeOff();
5409               DisplayInformation(_("End of game"));
5410             }
5411         }
5412       else
5413         {
5414           DisplayError(_("Incorrect move"), 0);
5415         }
5416       return 1;
5417     }
5418
5419   /* Ok, now we know that the move is good, so we can kill
5420      the previous line in Analysis Mode */
5421   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5422     {
5423       forwardMostMove = currentMove;
5424     }
5425
5426   /* If we need the chess program but it's dead, restart it */
5427   ResurrectChessProgram();
5428
5429   /* A user move restarts a paused game*/
5430   if (pausing)
5431     PauseEvent();
5432
5433   thinkOutput[0] = NULLCHAR;
5434
5435   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5436
5437   if (gameMode == BeginningOfGame)
5438     {
5439       if (appData.noChessProgram)
5440         {
5441           gameMode = EditGame;
5442           SetGameInfo();
5443         }
5444       else
5445         {
5446           char buf[MSG_SIZ];
5447           gameMode = MachinePlaysBlack;
5448           StartClocks();
5449           SetGameInfo();
5450           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5451           DisplayTitle(buf);
5452           if (first.sendName)
5453             {
5454               sprintf(buf, "name %s\n", gameInfo.white);
5455               SendToProgram(buf, &first);
5456             }
5457           StartClocks();
5458         }
5459       ModeHighlight();
5460     }
5461   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5462
5463   /* Relay move to ICS or chess engine */
5464   if (appData.icsActive)
5465     {
5466       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5467           gameMode == IcsExamining)
5468         {
5469           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5470           ics_user_moved = 1;
5471         }
5472     }
5473   else
5474     {
5475       if (first.sendTime && (gameMode == BeginningOfGame ||
5476                              gameMode == MachinePlaysWhite ||
5477                              gameMode == MachinePlaysBlack))
5478         {
5479           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5480         }
5481       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5482         {
5483           // [HGM] book: if program might be playing, let it use book
5484           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5485           first.maybeThinking = TRUE;
5486         }
5487       else
5488         SendMoveToProgram(forwardMostMove-1, &first);
5489       if (currentMove == cmailOldMove + 1)
5490         {
5491           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5492         }
5493     }
5494
5495   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5496
5497   switch (gameMode)
5498     {
5499     case EditGame:
5500       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5501                        EP_UNKNOWN, castlingRights[currentMove]) )
5502         {
5503         case MT_NONE:
5504         case MT_CHECK:
5505           break;
5506         case MT_CHECKMATE:
5507         case MT_STAINMATE:
5508           if (WhiteOnMove(currentMove))
5509             {
5510               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5511             }
5512           else
5513             {
5514               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5515             }
5516           break;
5517         case MT_STALEMATE:
5518           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5519           break;
5520     }
5521       break;
5522
5523     case MachinePlaysBlack:
5524     case MachinePlaysWhite:
5525       /* disable certain menu options while machine is thinking */
5526       SetMachineThinkingEnables();
5527       break;
5528
5529     default:
5530       break;
5531     }
5532
5533   if(bookHit)
5534     { // [HGM] book: simulate book reply
5535       static char bookMove[MSG_SIZ]; // a bit generous?
5536
5537       programStats.nodes = programStats.depth = programStats.time =
5538         programStats.score = programStats.got_only_move = 0;
5539       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5540
5541       strcpy(bookMove, "move ");
5542       strcat(bookMove, bookHit);
5543       HandleMachineMove(bookMove, &first);
5544     }
5545
5546   return 1;
5547 }
5548
5549 void
5550 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5551      int fromX, fromY, toX, toY;
5552      int promoChar;
5553 {
5554     /* [HGM] This routine was added to allow calling of its two logical
5555        parts from other modules in the old way. Before, UserMoveEvent()
5556        automatically called FinishMove() if the move was OK, and returned
5557        otherwise. I separated the two, in order to make it possible to
5558        slip a promotion popup in between. But that it always needs two
5559        calls, to the first part, (now called UserMoveTest() ), and to
5560        FinishMove if the first part succeeded. Calls that do not need
5561        to do anything in between, can call this routine the old way.
5562     */
5563     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5564 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5565     if(moveType == AmbiguousMove)
5566         DrawPosition(FALSE, boards[currentMove]);
5567     else if(moveType != ImpossibleMove && moveType != Comment)
5568         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5569 }
5570
5571 void LeftClick(ClickType clickType, int xPix, int yPix)
5572 {
5573     int x, y;
5574     Boolean saveAnimate;
5575     static int second = 0, promotionChoice = 0;
5576     char promoChoice = NULLCHAR;
5577
5578     if (clickType == Press) ErrorPopDown();
5579
5580     x = EventToSquare(xPix, BOARD_WIDTH);
5581     y = EventToSquare(yPix, BOARD_HEIGHT);
5582     if (!flipView && y >= 0) {
5583         y = BOARD_HEIGHT - 1 - y;
5584     }
5585     if (flipView && x >= 0) {
5586         x = BOARD_WIDTH - 1 - x;
5587     }
5588
5589     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5590         if(clickType == Release) return; // ignore upclick of click-click destination
5591         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5592         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5593         if(gameInfo.holdingsWidth && 
5594                 (WhiteOnMove(currentMove) 
5595                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5596                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5597             // click in right holdings, for determining promotion piece
5598             ChessSquare p = boards[currentMove][y][x];
5599             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5600             if(p != EmptySquare) {
5601                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5602                 fromX = fromY = -1;
5603                 return;
5604             }
5605         }
5606         DrawPosition(FALSE, boards[currentMove]);
5607         return;
5608     }
5609
5610     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5611     if(clickType == Press
5612             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5613               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5614               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5615         return;
5616
5617     if (fromX == -1) {
5618         if (clickType == Press) {
5619             /* First square */
5620             if (OKToStartUserMove(x, y)) {
5621                 fromX = x;
5622                 fromY = y;
5623                 second = 0;
5624                 DragPieceBegin(xPix, yPix);
5625                 if (appData.highlightDragging) {
5626                     SetHighlights(x, y, -1, -1);
5627                 }
5628             }
5629         }
5630         return;
5631     }
5632
5633     /* fromX != -1 */
5634     if (clickType == Press && gameMode != EditPosition) {
5635         ChessSquare fromP;
5636         ChessSquare toP;
5637         int frc;
5638
5639         // ignore off-board to clicks
5640         if(y < 0 || x < 0) return;
5641
5642         /* Check if clicking again on the same color piece */
5643         fromP = boards[currentMove][fromY][fromX];
5644         toP = boards[currentMove][y][x];
5645         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5646         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5647              WhitePawn <= toP && toP <= WhiteKing &&
5648              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5649              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5650             (BlackPawn <= fromP && fromP <= BlackKing && 
5651              BlackPawn <= toP && toP <= BlackKing &&
5652              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5653              !(fromP == BlackKing && toP == BlackRook && frc))) {
5654             /* Clicked again on same color piece -- changed his mind */
5655             second = (x == fromX && y == fromY);
5656             if (appData.highlightDragging) {
5657                 SetHighlights(x, y, -1, -1);
5658             } else {
5659                 ClearHighlights();
5660             }
5661             if (OKToStartUserMove(x, y)) {
5662                 fromX = x;
5663                 fromY = y;
5664                 DragPieceBegin(xPix, yPix);
5665             }
5666             return;
5667         }
5668         // ignore clicks on holdings
5669         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5670     }
5671
5672     if (clickType == Release && x == fromX && y == fromY) {
5673         DragPieceEnd(xPix, yPix);
5674         if (appData.animateDragging) {
5675             /* Undo animation damage if any */
5676             DrawPosition(FALSE, NULL);
5677         }
5678         if (second) {
5679             /* Second up/down in same square; just abort move */
5680             second = 0;
5681             fromX = fromY = -1;
5682             ClearHighlights();
5683             gotPremove = 0;
5684             ClearPremoveHighlights();
5685         } else {
5686             /* First upclick in same square; start click-click mode */
5687             SetHighlights(x, y, -1, -1);
5688         }
5689         return;
5690     }
5691
5692     /* we now have a different from- and (possibly off-board) to-square */
5693     /* Completed move */
5694     toX = x;
5695     toY = y;
5696     saveAnimate = appData.animate;
5697     if (clickType == Press) {
5698         /* Finish clickclick move */
5699         if (appData.animate || appData.highlightLastMove) {
5700             SetHighlights(fromX, fromY, toX, toY);
5701         } else {
5702             ClearHighlights();
5703         }
5704     } else {
5705         /* Finish drag move */
5706         if (appData.highlightLastMove) {
5707             SetHighlights(fromX, fromY, toX, toY);
5708         } else {
5709             ClearHighlights();
5710         }
5711         DragPieceEnd(xPix, yPix);
5712         /* Don't animate move and drag both */
5713         appData.animate = FALSE;
5714     }
5715
5716     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5717     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5718         ClearHighlights();
5719         fromX = fromY = -1;
5720         DrawPosition(FALSE, NULL);
5721         return;
5722     }
5723
5724     // off-board moves should not be highlighted
5725     if(x < 0 || x < 0) ClearHighlights();
5726
5727     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5728         SetHighlights(fromX, fromY, toX, toY);
5729         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5730             // [HGM] super: promotion to captured piece selected from holdings
5731             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5732             promotionChoice = TRUE;
5733             // kludge follows to temporarily execute move on display, without promoting yet
5734             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5735             boards[currentMove][toY][toX] = p;
5736             DrawPosition(FALSE, boards[currentMove]);
5737             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5738             boards[currentMove][toY][toX] = q;
5739             DisplayMessage("Click in holdings to choose piece", "");
5740             return;
5741         }
5742         PromotionPopUp();
5743     } else {
5744         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5745         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5746         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5747         fromX = fromY = -1;
5748     }
5749     appData.animate = saveAnimate;
5750     if (appData.animate || appData.animateDragging) {
5751         /* Undo animation damage if needed */
5752         DrawPosition(FALSE, NULL);
5753     }
5754 }
5755
5756 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5757 {
5758 //    char * hint = lastHint;
5759     FrontEndProgramStats stats;
5760
5761     stats.which = cps == &first ? 0 : 1;
5762     stats.depth = cpstats->depth;
5763     stats.nodes = cpstats->nodes;
5764     stats.score = cpstats->score;
5765     stats.time = cpstats->time;
5766     stats.pv = cpstats->movelist;
5767     stats.hint = lastHint;
5768     stats.an_move_index = 0;
5769     stats.an_move_count = 0;
5770
5771     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5772         stats.hint = cpstats->move_name;
5773         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5774         stats.an_move_count = cpstats->nr_moves;
5775     }
5776
5777     SetProgramStats( &stats );
5778 }
5779
5780 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5781 {   // [HGM] book: this routine intercepts moves to simulate book replies
5782     char *bookHit = NULL;
5783
5784     //first determine if the incoming move brings opponent into his book
5785     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5786         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5787     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5788     if(bookHit != NULL && !cps->bookSuspend) {
5789         // make sure opponent is not going to reply after receiving move to book position
5790         SendToProgram("force\n", cps);
5791         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5792     }
5793     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5794     // now arrange restart after book miss
5795     if(bookHit) {
5796         // after a book hit we never send 'go', and the code after the call to this routine
5797         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5798         char buf[MSG_SIZ];
5799         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5800         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5801         SendToProgram(buf, cps);
5802         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5803     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5804         SendToProgram("go\n", cps);
5805         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5806     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5807         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5808             SendToProgram("go\n", cps);
5809         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5810     }
5811     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5812 }
5813
5814 char *savedMessage;
5815 ChessProgramState *savedState;
5816 void DeferredBookMove(void)
5817 {
5818         if(savedState->lastPing != savedState->lastPong)
5819                     ScheduleDelayedEvent(DeferredBookMove, 10);
5820         else
5821         HandleMachineMove(savedMessage, savedState);
5822 }
5823
5824 void
5825 HandleMachineMove(message, cps)
5826      char *message;
5827      ChessProgramState *cps;
5828 {
5829     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5830     char realname[MSG_SIZ];
5831     int fromX, fromY, toX, toY;
5832     ChessMove moveType;
5833     char promoChar;
5834     char *p;
5835     int machineWhite;
5836     char *bookHit;
5837
5838 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5839     /*
5840      * Kludge to ignore BEL characters
5841      */
5842     while (*message == '\007') message++;
5843
5844     /*
5845      * [HGM] engine debug message: ignore lines starting with '#' character
5846      */
5847     if(cps->debug && *message == '#') return;
5848
5849     /*
5850      * Look for book output
5851      */
5852     if (cps == &first && bookRequested) {
5853         if (message[0] == '\t' || message[0] == ' ') {
5854             /* Part of the book output is here; append it */
5855             strcat(bookOutput, message);
5856             strcat(bookOutput, "  \n");
5857             return;
5858         } else if (bookOutput[0] != NULLCHAR) {
5859             /* All of book output has arrived; display it */
5860             char *p = bookOutput;
5861             while (*p != NULLCHAR) {
5862                 if (*p == '\t') *p = ' ';
5863                 p++;
5864             }
5865             DisplayInformation(bookOutput);
5866             bookRequested = FALSE;
5867             /* Fall through to parse the current output */
5868         }
5869     }
5870
5871     /*
5872      * Look for machine move.
5873      */
5874     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5875         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5876     {
5877         /* This method is only useful on engines that support ping */
5878         if (cps->lastPing != cps->lastPong) {
5879           if (gameMode == BeginningOfGame) {
5880             /* Extra move from before last new; ignore */
5881             if (appData.debugMode) {
5882                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5883             }
5884           } else {
5885             if (appData.debugMode) {
5886                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5887                         cps->which, gameMode);
5888             }
5889
5890             SendToProgram("undo\n", cps);
5891           }
5892           return;
5893         }
5894
5895         switch (gameMode) {
5896           case BeginningOfGame:
5897             /* Extra move from before last reset; ignore */
5898             if (appData.debugMode) {
5899                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5900             }
5901             return;
5902
5903           case EndOfGame:
5904           case IcsIdle:
5905           default:
5906             /* Extra move after we tried to stop.  The mode test is
5907                not a reliable way of detecting this problem, but it's
5908                the best we can do on engines that don't support ping.
5909             */
5910             if (appData.debugMode) {
5911                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5912                         cps->which, gameMode);
5913             }
5914             SendToProgram("undo\n", cps);
5915             return;
5916
5917           case MachinePlaysWhite:
5918           case IcsPlayingWhite:
5919             machineWhite = TRUE;
5920             break;
5921
5922           case MachinePlaysBlack:
5923           case IcsPlayingBlack:
5924             machineWhite = FALSE;
5925             break;
5926
5927           case TwoMachinesPlay:
5928             machineWhite = (cps->twoMachinesColor[0] == 'w');
5929             break;
5930         }
5931         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5932             if (appData.debugMode) {
5933                 fprintf(debugFP,
5934                         "Ignoring move out of turn by %s, gameMode %d"
5935                         ", forwardMost %d\n",
5936                         cps->which, gameMode, forwardMostMove);
5937             }
5938             return;
5939         }
5940
5941     if (appData.debugMode) { int f = forwardMostMove;
5942         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5943                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5944     }
5945         if(cps->alphaRank) AlphaRank(machineMove, 4);
5946         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5947                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5948             /* Machine move could not be parsed; ignore it. */
5949             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5950                     machineMove, cps->which);
5951             DisplayError(buf1, 0);
5952             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5953                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5954             if (gameMode == TwoMachinesPlay) {
5955               GameEnds(machineWhite ? BlackWins : WhiteWins,
5956                        buf1, GE_XBOARD);
5957             }
5958             return;
5959         }
5960
5961         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5962         /* So we have to redo legality test with true e.p. status here,  */
5963         /* to make sure an illegal e.p. capture does not slip through,   */
5964         /* to cause a forfeit on a justified illegal-move complaint      */
5965         /* of the opponent.                                              */
5966         if( gameMode==TwoMachinesPlay && appData.testLegality
5967             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5968                                                               ) {
5969            ChessMove moveType;
5970            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5971                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5972                              fromY, fromX, toY, toX, promoChar);
5973             if (appData.debugMode) {
5974                 int i;
5975                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5976                     castlingRights[forwardMostMove][i], castlingRank[i]);
5977                 fprintf(debugFP, "castling rights\n");
5978             }
5979             if(moveType == IllegalMove) {
5980                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5981                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5982                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5983                            buf1, GE_XBOARD);
5984                 return;
5985            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5986            /* [HGM] Kludge to handle engines that send FRC-style castling
5987               when they shouldn't (like TSCP-Gothic) */
5988            switch(moveType) {
5989              case WhiteASideCastleFR:
5990              case BlackASideCastleFR:
5991                toX+=2;
5992                currentMoveString[2]++;
5993                break;
5994              case WhiteHSideCastleFR:
5995              case BlackHSideCastleFR:
5996                toX--;
5997                currentMoveString[2]--;
5998                break;
5999              default: ; // nothing to do, but suppresses warning of pedantic compilers
6000            }
6001         }
6002         hintRequested = FALSE;
6003         lastHint[0] = NULLCHAR;
6004         bookRequested = FALSE;
6005         /* Program may be pondering now */
6006         cps->maybeThinking = TRUE;
6007         if (cps->sendTime == 2) cps->sendTime = 1;
6008         if (cps->offeredDraw) cps->offeredDraw--;
6009
6010 #if ZIPPY
6011         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6012             first.initDone) {
6013           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6014           ics_user_moved = 1;
6015           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6016                 char buf[3*MSG_SIZ];
6017
6018                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6019                         programStats.score / 100.,
6020                         programStats.depth,
6021                         programStats.time / 100.,
6022                         (unsigned int)programStats.nodes,
6023                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6024                         programStats.movelist);
6025                 SendToICS(buf);
6026 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6027           }
6028         }
6029 #endif
6030         /* currentMoveString is set as a side-effect of ParseOneMove */
6031         strcpy(machineMove, currentMoveString);
6032         strcat(machineMove, "\n");
6033         strcpy(moveList[forwardMostMove], machineMove);
6034
6035         /* [AS] Save move info and clear stats for next move */
6036         pvInfoList[ forwardMostMove ].score = programStats.score;
6037         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6038         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6039         ClearProgramStats();
6040         thinkOutput[0] = NULLCHAR;
6041         hiddenThinkOutputState = 0;
6042
6043         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6044
6045         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6046         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6047             int count = 0;
6048
6049             while( count < adjudicateLossPlies ) {
6050                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6051
6052                 if( count & 1 ) {
6053                     score = -score; /* Flip score for winning side */
6054                 }
6055
6056                 if( score > adjudicateLossThreshold ) {
6057                     break;
6058                 }
6059
6060                 count++;
6061             }
6062
6063             if( count >= adjudicateLossPlies ) {
6064                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6065
6066                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6067                     "Xboard adjudication",
6068                     GE_XBOARD );
6069
6070                 return;
6071             }
6072         }
6073
6074         if( gameMode == TwoMachinesPlay ) {
6075           // [HGM] some adjudications useful with buggy engines
6076             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6077           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6078
6079
6080             if( appData.testLegality )
6081             {   /* [HGM] Some more adjudications for obstinate engines */
6082                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6083                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6084                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6085                 static int moveCount = 6;
6086                 ChessMove result;
6087                 char *reason = NULL;
6088
6089                 /* Count what is on board. */
6090                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6091                 {   ChessSquare p = boards[forwardMostMove][i][j];
6092                     int m=i;
6093
6094                     switch((int) p)
6095                     {   /* count B,N,R and other of each side */
6096                         case WhiteKing:
6097                         case BlackKing:
6098                              NrK++; break; // [HGM] atomic: count Kings
6099                         case WhiteKnight:
6100                              NrWN++; break;
6101                         case WhiteBishop:
6102                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6103                              bishopsColor |= 1 << ((i^j)&1);
6104                              NrWB++; break;
6105                         case BlackKnight:
6106                              NrBN++; break;
6107                         case BlackBishop:
6108                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6109                              bishopsColor |= 1 << ((i^j)&1);
6110                              NrBB++; break;
6111                         case WhiteRook:
6112                              NrWR++; break;
6113                         case BlackRook:
6114                              NrBR++; break;
6115                         case WhiteQueen:
6116                              NrWQ++; break;
6117                         case BlackQueen:
6118                              NrBQ++; break;
6119                         case EmptySquare:
6120                              break;
6121                         case BlackPawn:
6122                              m = 7-i;
6123                         case WhitePawn:
6124                              PawnAdvance += m; NrPawns++;
6125                     }
6126                     NrPieces += (p != EmptySquare);
6127                     NrW += ((int)p < (int)BlackPawn);
6128                     if(gameInfo.variant == VariantXiangqi &&
6129                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6130                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6131                         NrW -= ((int)p < (int)BlackPawn);
6132                     }
6133                 }
6134
6135                 /* Some material-based adjudications that have to be made before stalemate test */
6136                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6137                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6138                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6139                      if(appData.checkMates) {
6140                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6141                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6142                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6143                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6144                          return;
6145                      }
6146                 }
6147
6148                 /* Bare King in Shatranj (loses) or Losers (wins) */
6149                 if( NrW == 1 || NrPieces - NrW == 1) {
6150                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6151                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6152                      if(appData.checkMates) {
6153                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6154                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6155                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6156                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6157                          return;
6158                      }
6159                   } else
6160                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6161                   {    /* bare King */
6162                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6163                         if(appData.checkMates) {
6164                             /* but only adjudicate if adjudication enabled */
6165                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6166                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6167                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6168                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6169                             return;
6170                         }
6171                   }
6172                 } else bare = 1;
6173
6174
6175             // don't wait for engine to announce game end if we can judge ourselves
6176             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6177                                        castlingRights[forwardMostMove]) ) {
6178               case MT_CHECK:
6179                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6180                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6181                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6182                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6183                             checkCnt++;
6184                         if(checkCnt >= 2) {
6185                             reason = "Xboard adjudication: 3rd check";
6186                             epStatus[forwardMostMove] = EP_CHECKMATE;
6187                             break;
6188                         }
6189                     }
6190                 }
6191               case MT_NONE:
6192               default:
6193                 break;
6194               case MT_STALEMATE:
6195               case MT_STAINMATE:
6196                 reason = "Xboard adjudication: Stalemate";
6197                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6198                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6199                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6200                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6201                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6202                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6203                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6204                                                                         EP_CHECKMATE : EP_WINS);
6205                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6206                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6207                 }
6208                 break;
6209               case MT_CHECKMATE:
6210                 reason = "Xboard adjudication: Checkmate";
6211                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6212                 break;
6213             }
6214
6215                 switch(i = epStatus[forwardMostMove]) {
6216                     case EP_STALEMATE:
6217                         result = GameIsDrawn; break;
6218                     case EP_CHECKMATE:
6219                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6220                     case EP_WINS:
6221                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6222                     default:
6223                         result = (ChessMove) 0;
6224                 }
6225                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6226                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6227                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6228                     GameEnds( result, reason, GE_XBOARD );
6229                     return;
6230                 }
6231
6232                 /* Next absolutely insufficient mating material. */
6233                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6234                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6235                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6236                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6237                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6238
6239                      /* always flag draws, for judging claims */
6240                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6241
6242                      if(appData.materialDraws) {
6243                          /* but only adjudicate them if adjudication enabled */
6244                          SendToProgram("force\n", cps->other); // suppress reply
6245                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6246                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6247                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6248                          return;
6249                      }
6250                 }
6251
6252                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6253                 if(NrPieces == 4 &&
6254                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6255                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6256                    || NrWN==2 || NrBN==2     /* KNNK */
6257                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6258                   ) ) {
6259                      if(--moveCount < 0 && appData.trivialDraws)
6260                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6261                           SendToProgram("force\n", cps->other); // suppress reply
6262                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6263                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6264                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6265                           return;
6266                      }
6267                 } else moveCount = 6;
6268             }
6269           }
6270           
6271           if (appData.debugMode) { int i;
6272             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6273                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6274                     appData.drawRepeats);
6275             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6276               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6277             
6278           }
6279
6280                 /* Check for rep-draws */
6281                 count = 0;
6282                 for(k = forwardMostMove-2;
6283                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6284                         epStatus[k] < EP_UNKNOWN &&
6285                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6286                     k-=2)
6287                 {   int rights=0;
6288                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6289                         /* compare castling rights */
6290                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6291                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6292                                 rights++; /* King lost rights, while rook still had them */
6293                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6294                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6295                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6296                                    rights++; /* but at least one rook lost them */
6297                         }
6298                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6299                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6300                                 rights++;
6301                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6302                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6303                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6304                                    rights++;
6305                         }
6306                         if( rights == 0 && ++count > appData.drawRepeats-2
6307                             && appData.drawRepeats > 1) {
6308                              /* adjudicate after user-specified nr of repeats */
6309                              SendToProgram("force\n", cps->other); // suppress reply
6310                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6311                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6312                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6313                                 // [HGM] xiangqi: check for forbidden perpetuals
6314                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6315                                 for(m=forwardMostMove; m>k; m-=2) {
6316                                     if(MateTest(boards[m], PosFlags(m),
6317                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6318                                         ourPerpetual = 0; // the current mover did not always check
6319                                     if(MateTest(boards[m-1], PosFlags(m-1),
6320                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6321                                         hisPerpetual = 0; // the opponent did not always check
6322                                 }
6323                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6324                                                                         ourPerpetual, hisPerpetual);
6325                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6326                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6327                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6328                                     return;
6329                                 }
6330                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6331                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6332                                 // Now check for perpetual chases
6333                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6334                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6335                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6336                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6337                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6338                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6339                                         return;
6340                                     }
6341                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6342                                         break; // Abort repetition-checking loop.
6343                                 }
6344                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6345                              }
6346                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6347                              return;
6348                         }
6349                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6350                              epStatus[forwardMostMove] = EP_REP_DRAW;
6351                     }
6352                 }
6353
6354                 /* Now we test for 50-move draws. Determine ply count */
6355                 count = forwardMostMove;
6356                 /* look for last irreversble move */
6357                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6358                     count--;
6359                 /* if we hit starting position, add initial plies */
6360                 if( count == backwardMostMove )
6361                     count -= initialRulePlies;
6362                 count = forwardMostMove - count;
6363                 if( count >= 100)
6364                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6365                          /* this is used to judge if draw claims are legal */
6366                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6367                          SendToProgram("force\n", cps->other); // suppress reply
6368                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6369                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6370                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6371                          return;
6372                 }
6373
6374                 /* if draw offer is pending, treat it as a draw claim
6375                  * when draw condition present, to allow engines a way to
6376                  * claim draws before making their move to avoid a race
6377                  * condition occurring after their move
6378                  */
6379                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6380                          char *p = NULL;
6381                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6382                              p = "Draw claim: 50-move rule";
6383                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6384                              p = "Draw claim: 3-fold repetition";
6385                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6386                              p = "Draw claim: insufficient mating material";
6387                          if( p != NULL ) {
6388                              SendToProgram("force\n", cps->other); // suppress reply
6389                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6390                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6391                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6392                              return;
6393                          }
6394                 }
6395
6396
6397                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6398                     SendToProgram("force\n", cps->other); // suppress reply
6399                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6400                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6401
6402                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6403
6404                     return;
6405                 }
6406         }
6407
6408         bookHit = NULL;
6409         if (gameMode == TwoMachinesPlay) {
6410             /* [HGM] relaying draw offers moved to after reception of move */
6411             /* and interpreting offer as claim if it brings draw condition */
6412             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6413                 SendToProgram("draw\n", cps->other);
6414             }
6415             if (cps->other->sendTime) {
6416                 SendTimeRemaining(cps->other,
6417                                   cps->other->twoMachinesColor[0] == 'w');
6418             }
6419             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6420             if (firstMove && !bookHit) {
6421                 firstMove = FALSE;
6422                 if (cps->other->useColors) {
6423                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6424                 }
6425                 SendToProgram("go\n", cps->other);
6426             }
6427             cps->other->maybeThinking = TRUE;
6428         }
6429
6430         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6431
6432         if (!pausing && appData.ringBellAfterMoves) {
6433             RingBell();
6434         }
6435
6436         /*
6437          * Reenable menu items that were disabled while
6438          * machine was thinking
6439          */
6440         if (gameMode != TwoMachinesPlay)
6441             SetUserThinkingEnables();
6442
6443         // [HGM] book: after book hit opponent has received move and is now in force mode
6444         // force the book reply into it, and then fake that it outputted this move by jumping
6445         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6446         if(bookHit) {
6447                 static char bookMove[MSG_SIZ]; // a bit generous?
6448
6449                 strcpy(bookMove, "move ");
6450                 strcat(bookMove, bookHit);
6451                 message = bookMove;
6452                 cps = cps->other;
6453                 programStats.nodes = programStats.depth = programStats.time =
6454                 programStats.score = programStats.got_only_move = 0;
6455                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6456
6457                 if(cps->lastPing != cps->lastPong) {
6458                     savedMessage = message; // args for deferred call
6459                     savedState = cps;
6460                     ScheduleDelayedEvent(DeferredBookMove, 10);
6461                     return;
6462                 }
6463                 goto FakeBookMove;
6464         }
6465
6466         return;
6467     }
6468
6469     /* Set special modes for chess engines.  Later something general
6470      *  could be added here; for now there is just one kludge feature,
6471      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6472      *  when "xboard" is given as an interactive command.
6473      */
6474     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6475         cps->useSigint = FALSE;
6476         cps->useSigterm = FALSE;
6477     }
6478     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6479       ParseFeatures(message+8, cps);
6480       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6481     }
6482
6483     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6484      * want this, I was asked to put it in, and obliged.
6485      */
6486     if (!strncmp(message, "setboard ", 9)) {
6487         Board initial_position; int i;
6488
6489         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6490
6491         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6492             DisplayError(_("Bad FEN received from engine"), 0);
6493             return ;
6494         } else {
6495            Reset(TRUE, FALSE);
6496            CopyBoard(boards[0], initial_position);
6497            initialRulePlies = FENrulePlies;
6498            epStatus[0] = FENepStatus;
6499            for( i=0; i<nrCastlingRights; i++ )
6500                 castlingRights[0][i] = FENcastlingRights[i];
6501            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6502            else gameMode = MachinePlaysBlack;
6503            DrawPosition(FALSE, boards[currentMove]);
6504         }
6505         return;
6506     }
6507
6508     /*
6509      * Look for communication commands
6510      */
6511     if (!strncmp(message, "telluser ", 9)) {
6512         DisplayNote(message + 9);
6513         return;
6514     }
6515     if (!strncmp(message, "tellusererror ", 14)) {
6516         DisplayError(message + 14, 0);
6517         return;
6518     }
6519     if (!strncmp(message, "tellopponent ", 13)) {
6520       if (appData.icsActive) {
6521         if (loggedOn) {
6522           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6523           SendToICS(buf1);
6524         }
6525       } else {
6526         DisplayNote(message + 13);
6527       }
6528       return;
6529     }
6530     if (!strncmp(message, "tellothers ", 11)) {
6531       if (appData.icsActive) {
6532         if (loggedOn) {
6533           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6534           SendToICS(buf1);
6535         }
6536       }
6537       return;
6538     }
6539     if (!strncmp(message, "tellall ", 8)) {
6540       if (appData.icsActive) {
6541         if (loggedOn) {
6542           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6543           SendToICS(buf1);
6544         }
6545       } else {
6546         DisplayNote(message + 8);
6547       }
6548       return;
6549     }
6550     if (strncmp(message, "warning", 7) == 0) {
6551         /* Undocumented feature, use tellusererror in new code */
6552         DisplayError(message, 0);
6553         return;
6554     }
6555     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6556         strcpy(realname, cps->tidy);
6557         strcat(realname, " query");
6558         AskQuestion(realname, buf2, buf1, cps->pr);
6559         return;
6560     }
6561     /* Commands from the engine directly to ICS.  We don't allow these to be
6562      *  sent until we are logged on. Crafty kibitzes have been known to
6563      *  interfere with the login process.
6564      */
6565     if (loggedOn) {
6566         if (!strncmp(message, "tellics ", 8)) {
6567             SendToICS(message + 8);
6568             SendToICS("\n");
6569             return;
6570         }
6571         if (!strncmp(message, "tellicsnoalias ", 15)) {
6572             SendToICS(ics_prefix);
6573             SendToICS(message + 15);
6574             SendToICS("\n");
6575             return;
6576         }
6577         /* The following are for backward compatibility only */
6578         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6579             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6580             SendToICS(ics_prefix);
6581             SendToICS(message);
6582             SendToICS("\n");
6583             return;
6584         }
6585     }
6586     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6587         return;
6588     }
6589     /*
6590      * If the move is illegal, cancel it and redraw the board.
6591      * Also deal with other error cases.  Matching is rather loose
6592      * here to accommodate engines written before the spec.
6593      */
6594     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6595         strncmp(message, "Error", 5) == 0) {
6596         if (StrStr(message, "name") ||
6597             StrStr(message, "rating") || StrStr(message, "?") ||
6598             StrStr(message, "result") || StrStr(message, "board") ||
6599             StrStr(message, "bk") || StrStr(message, "computer") ||
6600             StrStr(message, "variant") || StrStr(message, "hint") ||
6601             StrStr(message, "random") || StrStr(message, "depth") ||
6602             StrStr(message, "accepted")) {
6603             return;
6604         }
6605         if (StrStr(message, "protover")) {
6606           /* Program is responding to input, so it's apparently done
6607              initializing, and this error message indicates it is
6608              protocol version 1.  So we don't need to wait any longer
6609              for it to initialize and send feature commands. */
6610           FeatureDone(cps, 1);
6611           cps->protocolVersion = 1;
6612           return;
6613         }
6614         cps->maybeThinking = FALSE;
6615
6616         if (StrStr(message, "draw")) {
6617             /* Program doesn't have "draw" command */
6618             cps->sendDrawOffers = 0;
6619             return;
6620         }
6621         if (cps->sendTime != 1 &&
6622             (StrStr(message, "time") || StrStr(message, "otim"))) {
6623           /* Program apparently doesn't have "time" or "otim" command */
6624           cps->sendTime = 0;
6625           return;
6626         }
6627         if (StrStr(message, "analyze")) {
6628             cps->analysisSupport = FALSE;
6629             cps->analyzing = FALSE;
6630             Reset(FALSE, TRUE);
6631             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6632             DisplayError(buf2, 0);
6633             return;
6634         }
6635         if (StrStr(message, "(no matching move)st")) {
6636           /* Special kludge for GNU Chess 4 only */
6637           cps->stKludge = TRUE;
6638           SendTimeControl(cps, movesPerSession, timeControl,
6639                           timeIncrement, appData.searchDepth,
6640                           searchTime);
6641           return;
6642         }
6643         if (StrStr(message, "(no matching move)sd")) {
6644           /* Special kludge for GNU Chess 4 only */
6645           cps->sdKludge = TRUE;
6646           SendTimeControl(cps, movesPerSession, timeControl,
6647                           timeIncrement, appData.searchDepth,
6648                           searchTime);
6649           return;
6650         }
6651         if (!StrStr(message, "llegal")) {
6652             return;
6653         }
6654         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6655             gameMode == IcsIdle) return;
6656         if (forwardMostMove <= backwardMostMove) return;
6657         if (pausing) PauseEvent();
6658       if(appData.forceIllegal) {
6659             // [HGM] illegal: machine refused move; force position after move into it
6660           SendToProgram("force\n", cps);
6661           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6662                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6663                 // when black is to move, while there might be nothing on a2 or black
6664                 // might already have the move. So send the board as if white has the move.
6665                 // But first we must change the stm of the engine, as it refused the last move
6666                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6667                 if(WhiteOnMove(forwardMostMove)) {
6668                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6669                     SendBoard(cps, forwardMostMove); // kludgeless board
6670                 } else {
6671                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6672                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6673                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6674                 }
6675           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6676             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6677                  gameMode == TwoMachinesPlay)
6678               SendToProgram("go\n", cps);
6679             return;
6680       } else
6681         if (gameMode == PlayFromGameFile) {
6682             /* Stop reading this game file */
6683             gameMode = EditGame;
6684             ModeHighlight();
6685         }
6686         currentMove = --forwardMostMove;
6687         DisplayMove(currentMove-1); /* before DisplayMoveError */
6688         SwitchClocks();
6689         DisplayBothClocks();
6690         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6691                 parseList[currentMove], cps->which);
6692         DisplayMoveError(buf1);
6693         DrawPosition(FALSE, boards[currentMove]);
6694
6695         /* [HGM] illegal-move claim should forfeit game when Xboard */
6696         /* only passes fully legal moves                            */
6697         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6698             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6699                                 "False illegal-move claim", GE_XBOARD );
6700         }
6701         return;
6702     }
6703     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6704         /* Program has a broken "time" command that
6705            outputs a string not ending in newline.
6706            Don't use it. */
6707         cps->sendTime = 0;
6708     }
6709
6710     /*
6711      * If chess program startup fails, exit with an error message.
6712      * Attempts to recover here are futile.
6713      */
6714     if ((StrStr(message, "unknown host") != NULL)
6715         || (StrStr(message, "No remote directory") != NULL)
6716         || (StrStr(message, "not found") != NULL)
6717         || (StrStr(message, "No such file") != NULL)
6718         || (StrStr(message, "can't alloc") != NULL)
6719         || (StrStr(message, "Permission denied") != NULL)) {
6720
6721         cps->maybeThinking = FALSE;
6722         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6723                 cps->which, cps->program, cps->host, message);
6724         RemoveInputSource(cps->isr);
6725         DisplayFatalError(buf1, 0, 1);
6726         return;
6727     }
6728
6729     /*
6730      * Look for hint output
6731      */
6732     if (sscanf(message, "Hint: %s", buf1) == 1) {
6733         if (cps == &first && hintRequested) {
6734             hintRequested = FALSE;
6735             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6736                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6737                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6738                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6739                                     fromY, fromX, toY, toX, promoChar, buf1);
6740                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6741                 DisplayInformation(buf2);
6742             } else {
6743                 /* Hint move could not be parsed!? */
6744               snprintf(buf2, sizeof(buf2),
6745                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6746                         buf1, cps->which);
6747                 DisplayError(buf2, 0);
6748             }
6749         } else {
6750             strcpy(lastHint, buf1);
6751         }
6752         return;
6753     }
6754
6755     /*
6756      * Ignore other messages if game is not in progress
6757      */
6758     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6759         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6760
6761     /*
6762      * look for win, lose, draw, or draw offer
6763      */
6764     if (strncmp(message, "1-0", 3) == 0) {
6765         char *p, *q, *r = "";
6766         p = strchr(message, '{');
6767         if (p) {
6768             q = strchr(p, '}');
6769             if (q) {
6770                 *q = NULLCHAR;
6771                 r = p + 1;
6772             }
6773         }
6774         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6775         return;
6776     } else if (strncmp(message, "0-1", 3) == 0) {
6777         char *p, *q, *r = "";
6778         p = strchr(message, '{');
6779         if (p) {
6780             q = strchr(p, '}');
6781             if (q) {
6782                 *q = NULLCHAR;
6783                 r = p + 1;
6784             }
6785         }
6786         /* Kludge for Arasan 4.1 bug */
6787         if (strcmp(r, "Black resigns") == 0) {
6788             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6789             return;
6790         }
6791         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6792         return;
6793     } else if (strncmp(message, "1/2", 3) == 0) {
6794         char *p, *q, *r = "";
6795         p = strchr(message, '{');
6796         if (p) {
6797             q = strchr(p, '}');
6798             if (q) {
6799                 *q = NULLCHAR;
6800                 r = p + 1;
6801             }
6802         }
6803
6804         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6805         return;
6806
6807     } else if (strncmp(message, "White resign", 12) == 0) {
6808         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6809         return;
6810     } else if (strncmp(message, "Black resign", 12) == 0) {
6811         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6812         return;
6813     } else if (strncmp(message, "White matches", 13) == 0 ||
6814                strncmp(message, "Black matches", 13) == 0   ) {
6815         /* [HGM] ignore GNUShogi noises */
6816         return;
6817     } else if (strncmp(message, "White", 5) == 0 &&
6818                message[5] != '(' &&
6819                StrStr(message, "Black") == NULL) {
6820         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6821         return;
6822     } else if (strncmp(message, "Black", 5) == 0 &&
6823                message[5] != '(') {
6824         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6825         return;
6826     } else if (strcmp(message, "resign") == 0 ||
6827                strcmp(message, "computer resigns") == 0) {
6828         switch (gameMode) {
6829           case MachinePlaysBlack:
6830           case IcsPlayingBlack:
6831             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6832             break;
6833           case MachinePlaysWhite:
6834           case IcsPlayingWhite:
6835             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6836             break;
6837           case TwoMachinesPlay:
6838             if (cps->twoMachinesColor[0] == 'w')
6839               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6840             else
6841               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6842             break;
6843           default:
6844             /* can't happen */
6845             break;
6846         }
6847         return;
6848     } else if (strncmp(message, "opponent mates", 14) == 0) {
6849         switch (gameMode) {
6850           case MachinePlaysBlack:
6851           case IcsPlayingBlack:
6852             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6853             break;
6854           case MachinePlaysWhite:
6855           case IcsPlayingWhite:
6856             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6857             break;
6858           case TwoMachinesPlay:
6859             if (cps->twoMachinesColor[0] == 'w')
6860               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6861             else
6862               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6863             break;
6864           default:
6865             /* can't happen */
6866             break;
6867         }
6868         return;
6869     } else if (strncmp(message, "computer mates", 14) == 0) {
6870         switch (gameMode) {
6871           case MachinePlaysBlack:
6872           case IcsPlayingBlack:
6873             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6874             break;
6875           case MachinePlaysWhite:
6876           case IcsPlayingWhite:
6877             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6878             break;
6879           case TwoMachinesPlay:
6880             if (cps->twoMachinesColor[0] == 'w')
6881               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6882             else
6883               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6884             break;
6885           default:
6886             /* can't happen */
6887             break;
6888         }
6889         return;
6890     } else if (strncmp(message, "checkmate", 9) == 0) {
6891         if (WhiteOnMove(forwardMostMove)) {
6892             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6893         } else {
6894             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6895         }
6896         return;
6897     } else if (strstr(message, "Draw") != NULL ||
6898                strstr(message, "game is a draw") != NULL) {
6899         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6900         return;
6901     } else if (strstr(message, "offer") != NULL &&
6902                strstr(message, "draw") != NULL) {
6903 #if ZIPPY
6904         if (appData.zippyPlay && first.initDone) {
6905             /* Relay offer to ICS */
6906             SendToICS(ics_prefix);
6907             SendToICS("draw\n");
6908         }
6909 #endif
6910         cps->offeredDraw = 2; /* valid until this engine moves twice */
6911         if (gameMode == TwoMachinesPlay) {
6912             if (cps->other->offeredDraw) {
6913                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6914             /* [HGM] in two-machine mode we delay relaying draw offer      */
6915             /* until after we also have move, to see if it is really claim */
6916             }
6917         } else if (gameMode == MachinePlaysWhite ||
6918                    gameMode == MachinePlaysBlack) {
6919           if (userOfferedDraw) {
6920             DisplayInformation(_("Machine accepts your draw offer"));
6921             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6922           } else {
6923             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6924           }
6925         }
6926     }
6927
6928
6929     /*
6930      * Look for thinking output
6931      */
6932     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6933           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6934                                 ) {
6935         int plylev, mvleft, mvtot, curscore, time;
6936         char mvname[MOVE_LEN];
6937         u64 nodes; // [DM]
6938         char plyext;
6939         int ignore = FALSE;
6940         int prefixHint = FALSE;
6941         mvname[0] = NULLCHAR;
6942
6943         switch (gameMode) {
6944           case MachinePlaysBlack:
6945           case IcsPlayingBlack:
6946             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6947             break;
6948           case MachinePlaysWhite:
6949           case IcsPlayingWhite:
6950             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6951             break;
6952           case AnalyzeMode:
6953           case AnalyzeFile:
6954             break;
6955           case IcsObserving: /* [DM] icsEngineAnalyze */
6956             if (!appData.icsEngineAnalyze) ignore = TRUE;
6957             break;
6958           case TwoMachinesPlay:
6959             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6960                 ignore = TRUE;
6961             }
6962             break;
6963           default:
6964             ignore = TRUE;
6965             break;
6966         }
6967
6968         if (!ignore) {
6969             buf1[0] = NULLCHAR;
6970             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6971                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6972
6973                 if (plyext != ' ' && plyext != '\t') {
6974                     time *= 100;
6975                 }
6976
6977                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6978                 if( cps->scoreIsAbsolute &&
6979                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6980                 {
6981                     curscore = -curscore;
6982                 }
6983
6984
6985                 programStats.depth = plylev;
6986                 programStats.nodes = nodes;
6987                 programStats.time = time;
6988                 programStats.score = curscore;
6989                 programStats.got_only_move = 0;
6990
6991                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6992                         int ticklen;
6993
6994                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6995                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6996                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6997                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
6998                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6999                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7000                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7001                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7002                 }
7003
7004                 /* Buffer overflow protection */
7005                 if (buf1[0] != NULLCHAR) {
7006                     if (strlen(buf1) >= sizeof(programStats.movelist)
7007                         && appData.debugMode) {
7008                         fprintf(debugFP,
7009                                 "PV is too long; using the first %d bytes.\n",
7010                                 sizeof(programStats.movelist) - 1);
7011                     }
7012
7013                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7014                 } else {
7015                     sprintf(programStats.movelist, " no PV\n");
7016                 }
7017
7018                 if (programStats.seen_stat) {
7019                     programStats.ok_to_send = 1;
7020                 }
7021
7022                 if (strchr(programStats.movelist, '(') != NULL) {
7023                     programStats.line_is_book = 1;
7024                     programStats.nr_moves = 0;
7025                     programStats.moves_left = 0;
7026                 } else {
7027                     programStats.line_is_book = 0;
7028                 }
7029
7030                 SendProgramStatsToFrontend( cps, &programStats );
7031
7032                 /*
7033                     [AS] Protect the thinkOutput buffer from overflow... this
7034                     is only useful if buf1 hasn't overflowed first!
7035                 */
7036                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7037                         plylev,
7038                         (gameMode == TwoMachinesPlay ?
7039                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7040                         ((double) curscore) / 100.0,
7041                         prefixHint ? lastHint : "",
7042                         prefixHint ? " " : "" );
7043
7044                 if( buf1[0] != NULLCHAR ) {
7045                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7046
7047                     if( strlen(buf1) > max_len ) {
7048                         if( appData.debugMode) {
7049                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7050                         }
7051                         buf1[max_len+1] = '\0';
7052                     }
7053
7054                     strcat( thinkOutput, buf1 );
7055                 }
7056
7057                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7058                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7059                     DisplayMove(currentMove - 1);
7060                 }
7061                 return;
7062
7063             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7064                 /* crafty (9.25+) says "(only move) <move>"
7065                  * if there is only 1 legal move
7066                  */
7067                 sscanf(p, "(only move) %s", buf1);
7068                 sprintf(thinkOutput, "%s (only move)", buf1);
7069                 sprintf(programStats.movelist, "%s (only move)", buf1);
7070                 programStats.depth = 1;
7071                 programStats.nr_moves = 1;
7072                 programStats.moves_left = 1;
7073                 programStats.nodes = 1;
7074                 programStats.time = 1;
7075                 programStats.got_only_move = 1;
7076
7077                 /* Not really, but we also use this member to
7078                    mean "line isn't going to change" (Crafty
7079                    isn't searching, so stats won't change) */
7080                 programStats.line_is_book = 1;
7081
7082                 SendProgramStatsToFrontend( cps, &programStats );
7083
7084                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7085                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7086                     DisplayMove(currentMove - 1);
7087                 }
7088                 return;
7089             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7090                               &time, &nodes, &plylev, &mvleft,
7091                               &mvtot, mvname) >= 5) {
7092                 /* The stat01: line is from Crafty (9.29+) in response
7093                    to the "." command */
7094                 programStats.seen_stat = 1;
7095                 cps->maybeThinking = TRUE;
7096
7097                 if (programStats.got_only_move || !appData.periodicUpdates)
7098                   return;
7099
7100                 programStats.depth = plylev;
7101                 programStats.time = time;
7102                 programStats.nodes = nodes;
7103                 programStats.moves_left = mvleft;
7104                 programStats.nr_moves = mvtot;
7105                 strcpy(programStats.move_name, mvname);
7106                 programStats.ok_to_send = 1;
7107                 programStats.movelist[0] = '\0';
7108
7109                 SendProgramStatsToFrontend( cps, &programStats );
7110
7111                 return;
7112
7113             } else if (strncmp(message,"++",2) == 0) {
7114                 /* Crafty 9.29+ outputs this */
7115                 programStats.got_fail = 2;
7116                 return;
7117
7118             } else if (strncmp(message,"--",2) == 0) {
7119                 /* Crafty 9.29+ outputs this */
7120                 programStats.got_fail = 1;
7121                 return;
7122
7123             } else if (thinkOutput[0] != NULLCHAR &&
7124                        strncmp(message, "    ", 4) == 0) {
7125                 unsigned message_len;
7126
7127                 p = message;
7128                 while (*p && *p == ' ') p++;
7129
7130                 message_len = strlen( p );
7131
7132                 /* [AS] Avoid buffer overflow */
7133                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7134                     strcat(thinkOutput, " ");
7135                     strcat(thinkOutput, p);
7136                 }
7137
7138                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7139                     strcat(programStats.movelist, " ");
7140                     strcat(programStats.movelist, p);
7141                 }
7142
7143                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7144                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7145                     DisplayMove(currentMove - 1);
7146                 }
7147                 return;
7148             }
7149         }
7150         else {
7151             buf1[0] = NULLCHAR;
7152
7153             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7154                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7155             {
7156                 ChessProgramStats cpstats;
7157
7158                 if (plyext != ' ' && plyext != '\t') {
7159                     time *= 100;
7160                 }
7161
7162                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7163                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7164                     curscore = -curscore;
7165                 }
7166
7167                 cpstats.depth = plylev;
7168                 cpstats.nodes = nodes;
7169                 cpstats.time = time;
7170                 cpstats.score = curscore;
7171                 cpstats.got_only_move = 0;
7172                 cpstats.movelist[0] = '\0';
7173
7174                 if (buf1[0] != NULLCHAR) {
7175                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7176                 }
7177
7178                 cpstats.ok_to_send = 0;
7179                 cpstats.line_is_book = 0;
7180                 cpstats.nr_moves = 0;
7181                 cpstats.moves_left = 0;
7182
7183                 SendProgramStatsToFrontend( cps, &cpstats );
7184             }
7185         }
7186     }
7187 }
7188
7189
7190 /* Parse a game score from the character string "game", and
7191    record it as the history of the current game.  The game
7192    score is NOT assumed to start from the standard position.
7193    The display is not updated in any way.
7194    */
7195 void
7196 ParseGameHistory(game)
7197      char *game;
7198 {
7199     ChessMove moveType;
7200     int fromX, fromY, toX, toY, boardIndex;
7201     char promoChar;
7202     char *p, *q;
7203     char buf[MSG_SIZ];
7204
7205     if (appData.debugMode)
7206       fprintf(debugFP, "Parsing game history: %s\n", game);
7207
7208     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7209     gameInfo.site = StrSave(appData.icsHost);
7210     gameInfo.date = PGNDate();
7211     gameInfo.round = StrSave("-");
7212
7213     /* Parse out names of players */
7214     while (*game == ' ') game++;
7215     p = buf;
7216     while (*game != ' ') *p++ = *game++;
7217     *p = NULLCHAR;
7218     gameInfo.white = StrSave(buf);
7219     while (*game == ' ') game++;
7220     p = buf;
7221     while (*game != ' ' && *game != '\n') *p++ = *game++;
7222     *p = NULLCHAR;
7223     gameInfo.black = StrSave(buf);
7224
7225     /* Parse moves */
7226     boardIndex = blackPlaysFirst ? 1 : 0;
7227     yynewstr(game);
7228     for (;;) {
7229         yyboardindex = boardIndex;
7230         moveType = (ChessMove) yylex();
7231         switch (moveType) {
7232           case IllegalMove:             /* maybe suicide chess, etc. */
7233   if (appData.debugMode) {
7234     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7235     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7236     setbuf(debugFP, NULL);
7237   }
7238           case WhitePromotionChancellor:
7239           case BlackPromotionChancellor:
7240           case WhitePromotionArchbishop:
7241           case BlackPromotionArchbishop:
7242           case WhitePromotionQueen:
7243           case BlackPromotionQueen:
7244           case WhitePromotionRook:
7245           case BlackPromotionRook:
7246           case WhitePromotionBishop:
7247           case BlackPromotionBishop:
7248           case WhitePromotionKnight:
7249           case BlackPromotionKnight:
7250           case WhitePromotionKing:
7251           case BlackPromotionKing:
7252           case NormalMove:
7253           case WhiteCapturesEnPassant:
7254           case BlackCapturesEnPassant:
7255           case WhiteKingSideCastle:
7256           case WhiteQueenSideCastle:
7257           case BlackKingSideCastle:
7258           case BlackQueenSideCastle:
7259           case WhiteKingSideCastleWild:
7260           case WhiteQueenSideCastleWild:
7261           case BlackKingSideCastleWild:
7262           case BlackQueenSideCastleWild:
7263           /* PUSH Fabien */
7264           case WhiteHSideCastleFR:
7265           case WhiteASideCastleFR:
7266           case BlackHSideCastleFR:
7267           case BlackASideCastleFR:
7268           /* POP Fabien */
7269             fromX = currentMoveString[0] - AAA;
7270             fromY = currentMoveString[1] - ONE;
7271             toX = currentMoveString[2] - AAA;
7272             toY = currentMoveString[3] - ONE;
7273             promoChar = currentMoveString[4];
7274             break;
7275           case WhiteDrop:
7276           case BlackDrop:
7277             fromX = moveType == WhiteDrop ?
7278               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7279             (int) CharToPiece(ToLower(currentMoveString[0]));
7280             fromY = DROP_RANK;
7281             toX = currentMoveString[2] - AAA;
7282             toY = currentMoveString[3] - ONE;
7283             promoChar = NULLCHAR;
7284             break;
7285           case AmbiguousMove:
7286             /* bug? */
7287             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7288   if (appData.debugMode) {
7289     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7290     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7291     setbuf(debugFP, NULL);
7292   }
7293             DisplayError(buf, 0);
7294             return;
7295           case ImpossibleMove:
7296             /* bug? */
7297             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7298   if (appData.debugMode) {
7299     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7300     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7301     setbuf(debugFP, NULL);
7302   }
7303             DisplayError(buf, 0);
7304             return;
7305           case (ChessMove) 0:   /* end of file */
7306             if (boardIndex < backwardMostMove) {
7307                 /* Oops, gap.  How did that happen? */
7308                 DisplayError(_("Gap in move list"), 0);
7309                 return;
7310             }
7311             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7312             if (boardIndex > forwardMostMove) {
7313                 forwardMostMove = boardIndex;
7314             }
7315             return;
7316           case ElapsedTime:
7317             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7318                 strcat(parseList[boardIndex-1], " ");
7319                 strcat(parseList[boardIndex-1], yy_text);
7320             }
7321             continue;
7322           case Comment:
7323           case PGNTag:
7324           case NAG:
7325           default:
7326             /* ignore */
7327             continue;
7328           case WhiteWins:
7329           case BlackWins:
7330           case GameIsDrawn:
7331           case GameUnfinished:
7332             if (gameMode == IcsExamining) {
7333                 if (boardIndex < backwardMostMove) {
7334                     /* Oops, gap.  How did that happen? */
7335                     return;
7336                 }
7337                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7338                 return;
7339             }
7340             gameInfo.result = moveType;
7341             p = strchr(yy_text, '{');
7342             if (p == NULL) p = strchr(yy_text, '(');
7343             if (p == NULL) {
7344                 p = yy_text;
7345                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7346             } else {
7347                 q = strchr(p, *p == '{' ? '}' : ')');
7348                 if (q != NULL) *q = NULLCHAR;
7349                 p++;
7350             }
7351             gameInfo.resultDetails = StrSave(p);
7352             continue;
7353         }
7354         if (boardIndex >= forwardMostMove &&
7355             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7356             backwardMostMove = blackPlaysFirst ? 1 : 0;
7357             return;
7358         }
7359         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7360                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7361                                  parseList[boardIndex]);
7362         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7363         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7364         /* currentMoveString is set as a side-effect of yylex */
7365         strcpy(moveList[boardIndex], currentMoveString);
7366         strcat(moveList[boardIndex], "\n");
7367         boardIndex++;
7368         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7369                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7370         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7371                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7372           case MT_NONE:
7373           case MT_STALEMATE:
7374           default:
7375             break;
7376           case MT_CHECK:
7377             if(gameInfo.variant != VariantShogi)
7378                 strcat(parseList[boardIndex - 1], "+");
7379             break;
7380           case MT_CHECKMATE:
7381           case MT_STAINMATE:
7382             strcat(parseList[boardIndex - 1], "#");
7383             break;
7384         }
7385     }
7386 }
7387
7388
7389 /* Apply a move to the given board  */
7390 void
7391 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7392      int fromX, fromY, toX, toY;
7393      int promoChar;
7394      Board board;
7395      char *castling;
7396      char *ep;
7397 {
7398   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7399
7400     /* [HGM] compute & store e.p. status and castling rights for new position */
7401     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7402     { int i;
7403
7404       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7405       oldEP = *ep;
7406       *ep = EP_NONE;
7407
7408       if( board[toY][toX] != EmptySquare )
7409            *ep = EP_CAPTURE;
7410
7411       if( board[fromY][fromX] == WhitePawn ) {
7412            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7413                *ep = EP_PAWN_MOVE;
7414            if( toY-fromY==2) {
7415                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7416                         gameInfo.variant != VariantBerolina || toX < fromX)
7417                       *ep = toX | berolina;
7418                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7419                         gameInfo.variant != VariantBerolina || toX > fromX)
7420                       *ep = toX;
7421            }
7422       } else
7423       if( board[fromY][fromX] == BlackPawn ) {
7424            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7425                *ep = EP_PAWN_MOVE;
7426            if( toY-fromY== -2) {
7427                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7428                         gameInfo.variant != VariantBerolina || toX < fromX)
7429                       *ep = toX | berolina;
7430                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7431                         gameInfo.variant != VariantBerolina || toX > fromX)
7432                       *ep = toX;
7433            }
7434        }
7435
7436        for(i=0; i<nrCastlingRights; i++) {
7437            if(castling[i] == fromX && castlingRank[i] == fromY ||
7438               castling[i] == toX   && castlingRank[i] == toY
7439              ) castling[i] = -1; // revoke for moved or captured piece
7440        }
7441
7442     }
7443
7444   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7445   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7446        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7447
7448   if (fromX == toX && fromY == toY) return;
7449
7450   if (fromY == DROP_RANK) {
7451         /* must be first */
7452         piece = board[toY][toX] = (ChessSquare) fromX;
7453   } else {
7454      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7455      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7456      if(gameInfo.variant == VariantKnightmate)
7457          king += (int) WhiteUnicorn - (int) WhiteKing;
7458
7459     /* Code added by Tord: */
7460     /* FRC castling assumed when king captures friendly rook. */
7461     if (board[fromY][fromX] == WhiteKing &&
7462              board[toY][toX] == WhiteRook) {
7463       board[fromY][fromX] = EmptySquare;
7464       board[toY][toX] = EmptySquare;
7465       if(toX > fromX) {
7466         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7467       } else {
7468         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7469       }
7470     } else if (board[fromY][fromX] == BlackKing &&
7471                board[toY][toX] == BlackRook) {
7472       board[fromY][fromX] = EmptySquare;
7473       board[toY][toX] = EmptySquare;
7474       if(toX > fromX) {
7475         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7476       } else {
7477         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7478       }
7479     /* End of code added by Tord */
7480
7481     } else if (board[fromY][fromX] == king
7482         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7483         && toY == fromY && toX > fromX+1) {
7484         board[fromY][fromX] = EmptySquare;
7485         board[toY][toX] = king;
7486         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7487         board[fromY][BOARD_RGHT-1] = EmptySquare;
7488     } else if (board[fromY][fromX] == king
7489         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7490                && toY == fromY && toX < fromX-1) {
7491         board[fromY][fromX] = EmptySquare;
7492         board[toY][toX] = king;
7493         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7494         board[fromY][BOARD_LEFT] = EmptySquare;
7495     } else if (board[fromY][fromX] == WhitePawn
7496                && toY == BOARD_HEIGHT-1
7497                && gameInfo.variant != VariantXiangqi
7498                ) {
7499         /* white pawn promotion */
7500         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7501         if (board[toY][toX] == EmptySquare) {
7502             board[toY][toX] = WhiteQueen;
7503         }
7504         if(gameInfo.variant==VariantBughouse ||
7505            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7506             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7507         board[fromY][fromX] = EmptySquare;
7508     } else if ((fromY == BOARD_HEIGHT-4)
7509                && (toX != fromX)
7510                && gameInfo.variant != VariantXiangqi
7511                && gameInfo.variant != VariantBerolina
7512                && (board[fromY][fromX] == WhitePawn)
7513                && (board[toY][toX] == EmptySquare)) {
7514         board[fromY][fromX] = EmptySquare;
7515         board[toY][toX] = WhitePawn;
7516         captured = board[toY - 1][toX];
7517         board[toY - 1][toX] = EmptySquare;
7518     } else if ((fromY == BOARD_HEIGHT-4)
7519                && (toX == fromX)
7520                && gameInfo.variant == VariantBerolina
7521                && (board[fromY][fromX] == WhitePawn)
7522                && (board[toY][toX] == EmptySquare)) {
7523         board[fromY][fromX] = EmptySquare;
7524         board[toY][toX] = WhitePawn;
7525         if(oldEP & EP_BEROLIN_A) {
7526                 captured = board[fromY][fromX-1];
7527                 board[fromY][fromX-1] = EmptySquare;
7528         }else{  captured = board[fromY][fromX+1];
7529                 board[fromY][fromX+1] = EmptySquare;
7530         }
7531     } else if (board[fromY][fromX] == king
7532         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7533                && toY == fromY && toX > fromX+1) {
7534         board[fromY][fromX] = EmptySquare;
7535         board[toY][toX] = king;
7536         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7537         board[fromY][BOARD_RGHT-1] = EmptySquare;
7538     } else if (board[fromY][fromX] == king
7539         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7540                && toY == fromY && toX < fromX-1) {
7541         board[fromY][fromX] = EmptySquare;
7542         board[toY][toX] = king;
7543         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7544         board[fromY][BOARD_LEFT] = EmptySquare;
7545     } else if (fromY == 7 && fromX == 3
7546                && board[fromY][fromX] == BlackKing
7547                && toY == 7 && toX == 5) {
7548         board[fromY][fromX] = EmptySquare;
7549         board[toY][toX] = BlackKing;
7550         board[fromY][7] = EmptySquare;
7551         board[toY][4] = BlackRook;
7552     } else if (fromY == 7 && fromX == 3
7553                && board[fromY][fromX] == BlackKing
7554                && toY == 7 && toX == 1) {
7555         board[fromY][fromX] = EmptySquare;
7556         board[toY][toX] = BlackKing;
7557         board[fromY][0] = EmptySquare;
7558         board[toY][2] = BlackRook;
7559     } else if (board[fromY][fromX] == BlackPawn
7560                && toY == 0
7561                && gameInfo.variant != VariantXiangqi
7562                ) {
7563         /* black pawn promotion */
7564         board[0][toX] = CharToPiece(ToLower(promoChar));
7565         if (board[0][toX] == EmptySquare) {
7566             board[0][toX] = BlackQueen;
7567         }
7568         if(gameInfo.variant==VariantBughouse ||
7569            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7570             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7571         board[fromY][fromX] = EmptySquare;
7572     } else if ((fromY == 3)
7573                && (toX != fromX)
7574                && gameInfo.variant != VariantXiangqi
7575                && gameInfo.variant != VariantBerolina
7576                && (board[fromY][fromX] == BlackPawn)
7577                && (board[toY][toX] == EmptySquare)) {
7578         board[fromY][fromX] = EmptySquare;
7579         board[toY][toX] = BlackPawn;
7580         captured = board[toY + 1][toX];
7581         board[toY + 1][toX] = EmptySquare;
7582     } else if ((fromY == 3)
7583                && (toX == fromX)
7584                && gameInfo.variant == VariantBerolina
7585                && (board[fromY][fromX] == BlackPawn)
7586                && (board[toY][toX] == EmptySquare)) {
7587         board[fromY][fromX] = EmptySquare;
7588         board[toY][toX] = BlackPawn;
7589         if(oldEP & EP_BEROLIN_A) {
7590                 captured = board[fromY][fromX-1];
7591                 board[fromY][fromX-1] = EmptySquare;
7592         }else{  captured = board[fromY][fromX+1];
7593                 board[fromY][fromX+1] = EmptySquare;
7594         }
7595     } else {
7596         board[toY][toX] = board[fromY][fromX];
7597         board[fromY][fromX] = EmptySquare;
7598     }
7599
7600     /* [HGM] now we promote for Shogi, if needed */
7601     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7602         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7603   }
7604
7605     if (gameInfo.holdingsWidth != 0) {
7606
7607       /* !!A lot more code needs to be written to support holdings  */
7608       /* [HGM] OK, so I have written it. Holdings are stored in the */
7609       /* penultimate board files, so they are automaticlly stored   */
7610       /* in the game history.                                       */
7611       if (fromY == DROP_RANK) {
7612         /* Delete from holdings, by decreasing count */
7613         /* and erasing image if necessary            */
7614         p = (int) fromX;
7615         if(p < (int) BlackPawn) { /* white drop */
7616              p -= (int)WhitePawn;
7617                  p = PieceToNumber((ChessSquare)p);
7618              if(p >= gameInfo.holdingsSize) p = 0;
7619              if(--board[p][BOARD_WIDTH-2] <= 0)
7620                   board[p][BOARD_WIDTH-1] = EmptySquare;
7621              if((int)board[p][BOARD_WIDTH-2] < 0)
7622                         board[p][BOARD_WIDTH-2] = 0;
7623         } else {                  /* black drop */
7624              p -= (int)BlackPawn;
7625                  p = PieceToNumber((ChessSquare)p);
7626              if(p >= gameInfo.holdingsSize) p = 0;
7627              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7628                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7629              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7630                         board[BOARD_HEIGHT-1-p][1] = 0;
7631         }
7632       }
7633       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7634           && gameInfo.variant != VariantBughouse        ) {
7635         /* [HGM] holdings: Add to holdings, if holdings exist */
7636         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7637                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7638                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7639         }
7640         p = (int) captured;
7641         if (p >= (int) BlackPawn) {
7642           p -= (int)BlackPawn;
7643           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7644                   /* in Shogi restore piece to its original  first */
7645                   captured = (ChessSquare) (DEMOTED captured);
7646                   p = DEMOTED p;
7647           }
7648           p = PieceToNumber((ChessSquare)p);
7649           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7650           board[p][BOARD_WIDTH-2]++;
7651           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7652         } else {
7653           p -= (int)WhitePawn;
7654           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7655                   captured = (ChessSquare) (DEMOTED captured);
7656                   p = DEMOTED p;
7657           }
7658           p = PieceToNumber((ChessSquare)p);
7659           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7660           board[BOARD_HEIGHT-1-p][1]++;
7661           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7662         }
7663       }
7664     } else if (gameInfo.variant == VariantAtomic) {
7665       if (captured != EmptySquare) {
7666         int y, x;
7667         for (y = toY-1; y <= toY+1; y++) {
7668           for (x = toX-1; x <= toX+1; x++) {
7669             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7670                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7671               board[y][x] = EmptySquare;
7672             }
7673           }
7674         }
7675         board[toY][toX] = EmptySquare;
7676       }
7677     }
7678     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7679         /* [HGM] Shogi promotions */
7680         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7681     }
7682
7683     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7684                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7685         // [HGM] superchess: take promotion piece out of holdings
7686         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7687         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7688             if(!--board[k][BOARD_WIDTH-2])
7689                 board[k][BOARD_WIDTH-1] = EmptySquare;
7690         } else {
7691             if(!--board[BOARD_HEIGHT-1-k][1])
7692                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7693         }
7694     }
7695
7696 }
7697
7698 /* Updates forwardMostMove */
7699 void
7700 MakeMove(fromX, fromY, toX, toY, promoChar)
7701      int fromX, fromY, toX, toY;
7702      int promoChar;
7703 {
7704 //    forwardMostMove++; // [HGM] bare: moved downstream
7705
7706     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7707         int timeLeft; static int lastLoadFlag=0; int king, piece;
7708         piece = boards[forwardMostMove][fromY][fromX];
7709         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7710         if(gameInfo.variant == VariantKnightmate)
7711             king += (int) WhiteUnicorn - (int) WhiteKing;
7712         if(forwardMostMove == 0) {
7713             if(blackPlaysFirst)
7714                 fprintf(serverMoves, "%s;", second.tidy);
7715             fprintf(serverMoves, "%s;", first.tidy);
7716             if(!blackPlaysFirst)
7717                 fprintf(serverMoves, "%s;", second.tidy);
7718         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7719         lastLoadFlag = loadFlag;
7720         // print base move
7721         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7722         // print castling suffix
7723         if( toY == fromY && piece == king ) {
7724             if(toX-fromX > 1)
7725                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7726             if(fromX-toX >1)
7727                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7728         }
7729         // e.p. suffix
7730         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7731              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7732              boards[forwardMostMove][toY][toX] == EmptySquare
7733              && fromX != toX )
7734                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7735         // promotion suffix
7736         if(promoChar != NULLCHAR)
7737                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7738         if(!loadFlag) {
7739             fprintf(serverMoves, "/%d/%d",
7740                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7741             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7742             else                      timeLeft = blackTimeRemaining/1000;
7743             fprintf(serverMoves, "/%d", timeLeft);
7744         }
7745         fflush(serverMoves);
7746     }
7747
7748     if (forwardMostMove+1 >= MAX_MOVES) {
7749       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7750                         0, 1);
7751       return;
7752     }
7753     if (commentList[forwardMostMove+1] != NULL) {
7754         free(commentList[forwardMostMove+1]);
7755         commentList[forwardMostMove+1] = NULL;
7756     }
7757     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7758     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7759     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7760                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7761     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7762     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7763     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7764     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7765     gameInfo.result = GameUnfinished;
7766     if (gameInfo.resultDetails != NULL) {
7767         free(gameInfo.resultDetails);
7768         gameInfo.resultDetails = NULL;
7769     }
7770     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7771                               moveList[forwardMostMove - 1]);
7772     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7773                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7774                              fromY, fromX, toY, toX, promoChar,
7775                              parseList[forwardMostMove - 1]);
7776     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7777                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7778                             castlingRights[forwardMostMove]) ) {
7779       case MT_NONE:
7780       case MT_STALEMATE:
7781       default:
7782         break;
7783       case MT_CHECK:
7784         if(gameInfo.variant != VariantShogi)
7785             strcat(parseList[forwardMostMove - 1], "+");
7786         break;
7787       case MT_CHECKMATE:
7788       case MT_STAINMATE:
7789         strcat(parseList[forwardMostMove - 1], "#");
7790         break;
7791     }
7792     if (appData.debugMode) {
7793         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7794     }
7795
7796 }
7797
7798 /* Updates currentMove if not pausing */
7799 void
7800 ShowMove(fromX, fromY, toX, toY)
7801 {
7802     int instant = (gameMode == PlayFromGameFile) ?
7803         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7804
7805     if(appData.noGUI) return;
7806
7807     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7808       {
7809         if (!instant)
7810           {
7811             if (forwardMostMove == currentMove + 1)
7812               {
7813 //TODO
7814 //              AnimateMove(boards[forwardMostMove - 1],
7815 //                          fromX, fromY, toX, toY);
7816               }
7817             if (appData.highlightLastMove)
7818               {
7819                 SetHighlights(fromX, fromY, toX, toY);
7820               }
7821           }
7822         currentMove = forwardMostMove;
7823     }
7824
7825     if (instant) return;
7826
7827     DisplayMove(currentMove - 1);
7828     DrawPosition(FALSE, boards[currentMove]);
7829     DisplayBothClocks();
7830     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7831
7832     return;
7833 }
7834
7835 void SendEgtPath(ChessProgramState *cps)
7836 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7837         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7838
7839         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7840
7841         while(*p) {
7842             char c, *q = name+1, *r, *s;
7843
7844             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7845             while(*p && *p != ',') *q++ = *p++;
7846             *q++ = ':'; *q = 0;
7847             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7848                 strcmp(name, ",nalimov:") == 0 ) {
7849                 // take nalimov path from the menu-changeable option first, if it is defined
7850                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7851                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7852             } else
7853             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7854                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7855                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7856                 s = r = StrStr(s, ":") + 1; // beginning of path info
7857                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7858                 c = *r; *r = 0;             // temporarily null-terminate path info
7859                     *--q = 0;               // strip of trailig ':' from name
7860                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7861                 *r = c;
7862                 SendToProgram(buf,cps);     // send egtbpath command for this format
7863             }
7864             if(*p == ',') p++; // read away comma to position for next format name
7865         }
7866 }
7867
7868 void
7869 InitChessProgram(cps, setup)
7870      ChessProgramState *cps;
7871      int setup; /* [HGM] needed to setup FRC opening position */
7872 {
7873     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7874     if (appData.noChessProgram) return;
7875     hintRequested = FALSE;
7876     bookRequested = FALSE;
7877
7878     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7879     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7880     if(cps->memSize) { /* [HGM] memory */
7881         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7882         SendToProgram(buf, cps);
7883     }
7884     SendEgtPath(cps); /* [HGM] EGT */
7885     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7886         sprintf(buf, "cores %d\n", appData.smpCores);
7887         SendToProgram(buf, cps);
7888     }
7889
7890     SendToProgram(cps->initString, cps);
7891     if (gameInfo.variant != VariantNormal &&
7892         gameInfo.variant != VariantLoadable
7893         /* [HGM] also send variant if board size non-standard */
7894         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7895                                             ) {
7896       char *v = VariantName(gameInfo.variant);
7897       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7898         /* [HGM] in protocol 1 we have to assume all variants valid */
7899         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7900         DisplayFatalError(buf, 0, 1);
7901         return;
7902       }
7903
7904       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7905       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7906       if( gameInfo.variant == VariantXiangqi )
7907            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7908       if( gameInfo.variant == VariantShogi )
7909            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7910       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7911            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7912       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7913                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7914            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7915       if( gameInfo.variant == VariantCourier )
7916            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7917       if( gameInfo.variant == VariantSuper )
7918            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7919       if( gameInfo.variant == VariantGreat )
7920            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7921
7922       if(overruled) {
7923            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7924                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7925            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7926            if(StrStr(cps->variants, b) == NULL) {
7927                // specific sized variant not known, check if general sizing allowed
7928                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7929                    if(StrStr(cps->variants, "boardsize") == NULL) {
7930                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7931                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7932                        DisplayFatalError(buf, 0, 1);
7933                        return;
7934                    }
7935                    /* [HGM] here we really should compare with the maximum supported board size */
7936                }
7937            }
7938       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7939       sprintf(buf, "variant %s\n", b);
7940       SendToProgram(buf, cps);
7941     }
7942     currentlyInitializedVariant = gameInfo.variant;
7943
7944     /* [HGM] send opening position in FRC to first engine */
7945     if(setup) {
7946           SendToProgram("force\n", cps);
7947           SendBoard(cps, 0);
7948           /* engine is now in force mode! Set flag to wake it up after first move. */
7949           setboardSpoiledMachineBlack = 1;
7950     }
7951
7952     if (cps->sendICS) {
7953       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7954       SendToProgram(buf, cps);
7955     }
7956     cps->maybeThinking = FALSE;
7957     cps->offeredDraw = 0;
7958     if (!appData.icsActive) {
7959         SendTimeControl(cps, movesPerSession, timeControl,
7960                         timeIncrement, appData.searchDepth,
7961                         searchTime);
7962     }
7963     if (appData.showThinking
7964         // [HGM] thinking: four options require thinking output to be sent
7965         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7966                                 ) {
7967         SendToProgram("post\n", cps);
7968     }
7969     SendToProgram("hard\n", cps);
7970     if (!appData.ponderNextMove) {
7971         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7972            it without being sure what state we are in first.  "hard"
7973            is not a toggle, so that one is OK.
7974          */
7975         SendToProgram("easy\n", cps);
7976     }
7977     if (cps->usePing) {
7978       sprintf(buf, "ping %d\n", ++cps->lastPing);
7979       SendToProgram(buf, cps);
7980     }
7981     cps->initDone = TRUE;
7982 }
7983
7984
7985 void
7986 StartChessProgram(cps)
7987      ChessProgramState *cps;
7988 {
7989     char buf[MSG_SIZ];
7990     int err;
7991
7992     if (appData.noChessProgram) return;
7993     cps->initDone = FALSE;
7994
7995     if (strcmp(cps->host, "localhost") == 0) {
7996         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7997     } else if (*appData.remoteShell == NULLCHAR) {
7998         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7999     } else {
8000         if (*appData.remoteUser == NULLCHAR) {
8001           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8002                     cps->program);
8003         } else {
8004           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8005                     cps->host, appData.remoteUser, cps->program);
8006         }
8007         err = StartChildProcess(buf, "", &cps->pr);
8008     }
8009
8010     if (err != 0) {
8011         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8012         DisplayFatalError(buf, err, 1);
8013         cps->pr = NoProc;
8014         cps->isr = NULL;
8015         return;
8016     }
8017
8018     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8019     if (cps->protocolVersion > 1) {
8020       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8021       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8022       cps->comboCnt = 0;  //                and values of combo boxes
8023       SendToProgram(buf, cps);
8024     } else {
8025       SendToProgram("xboard\n", cps);
8026     }
8027 }
8028
8029
8030 void
8031 TwoMachinesEventIfReady P((void))
8032 {
8033   if (first.lastPing != first.lastPong) {
8034     DisplayMessage("", _("Waiting for first chess program"));
8035     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8036     return;
8037   }
8038   if (second.lastPing != second.lastPong) {
8039     DisplayMessage("", _("Waiting for second chess program"));
8040     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8041     return;
8042   }
8043   ThawUI();
8044   TwoMachinesEvent();
8045 }
8046
8047 void
8048 NextMatchGame P((void))
8049 {
8050     int index; /* [HGM] autoinc: step load index during match */
8051     Reset(FALSE, TRUE);
8052     if (*appData.loadGameFile != NULLCHAR) {
8053         index = appData.loadGameIndex;
8054         if(index < 0) { // [HGM] autoinc
8055             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8056             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8057         }
8058         LoadGameFromFile(appData.loadGameFile,
8059                          index,
8060                          appData.loadGameFile, FALSE);
8061     } else if (*appData.loadPositionFile != NULLCHAR) {
8062         index = appData.loadPositionIndex;
8063         if(index < 0) { // [HGM] autoinc
8064             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8065             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8066         }
8067         LoadPositionFromFile(appData.loadPositionFile,
8068                              index,
8069                              appData.loadPositionFile);
8070     }
8071     TwoMachinesEventIfReady();
8072 }
8073
8074 void UserAdjudicationEvent( int result )
8075 {
8076     ChessMove gameResult = GameIsDrawn;
8077
8078     if( result > 0 ) {
8079         gameResult = WhiteWins;
8080     }
8081     else if( result < 0 ) {
8082         gameResult = BlackWins;
8083     }
8084
8085     if( gameMode == TwoMachinesPlay ) {
8086         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8087     }
8088 }
8089
8090
8091 // [HGM] save: calculate checksum of game to make games easily identifiable
8092 int StringCheckSum(char *s)
8093 {
8094         int i = 0;
8095         if(s==NULL) return 0;
8096         while(*s) i = i*259 + *s++;
8097         return i;
8098 }
8099
8100 int GameCheckSum()
8101 {
8102         int i, sum=0;
8103         for(i=backwardMostMove; i<forwardMostMove; i++) {
8104                 sum += pvInfoList[i].depth;
8105                 sum += StringCheckSum(parseList[i]);
8106                 sum += StringCheckSum(commentList[i]);
8107                 sum *= 261;
8108         }
8109         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8110         return sum + StringCheckSum(commentList[i]);
8111 } // end of save patch
8112
8113 void
8114 GameEnds(result, resultDetails, whosays)
8115      ChessMove result;
8116      char *resultDetails;
8117      int whosays;
8118 {
8119     GameMode nextGameMode;
8120     int isIcsGame;
8121     char buf[MSG_SIZ];
8122
8123     if(endingGame) return; /* [HGM] crash: forbid recursion */
8124     endingGame = 1;
8125
8126     if (appData.debugMode) {
8127       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8128               result, resultDetails ? resultDetails : "(null)", whosays);
8129     }
8130
8131     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8132         /* If we are playing on ICS, the server decides when the
8133            game is over, but the engine can offer to draw, claim
8134            a draw, or resign.
8135          */
8136 #if ZIPPY
8137         if (appData.zippyPlay && first.initDone) {
8138             if (result == GameIsDrawn) {
8139                 /* In case draw still needs to be claimed */
8140                 SendToICS(ics_prefix);
8141                 SendToICS("draw\n");
8142             } else if (StrCaseStr(resultDetails, "resign")) {
8143                 SendToICS(ics_prefix);
8144                 SendToICS("resign\n");
8145             }
8146         }
8147 #endif
8148         endingGame = 0; /* [HGM] crash */
8149         return;
8150     }
8151
8152     /* If we're loading the game from a file, stop */
8153     if (whosays == GE_FILE) {
8154       (void) StopLoadGameTimer();
8155       gameFileFP = NULL;
8156     }
8157
8158     /* Cancel draw offers */
8159     first.offeredDraw = second.offeredDraw = 0;
8160
8161     /* If this is an ICS game, only ICS can really say it's done;
8162        if not, anyone can. */
8163     isIcsGame = (gameMode == IcsPlayingWhite ||
8164                  gameMode == IcsPlayingBlack ||
8165                  gameMode == IcsObserving    ||
8166                  gameMode == IcsExamining);
8167
8168     if (!isIcsGame || whosays == GE_ICS) {
8169         /* OK -- not an ICS game, or ICS said it was done */
8170         StopClocks();
8171         if (!isIcsGame && !appData.noChessProgram)
8172           SetUserThinkingEnables();
8173
8174         /* [HGM] if a machine claims the game end we verify this claim */
8175         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8176             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8177                 char claimer;
8178                 ChessMove trueResult = (ChessMove) -1;
8179
8180                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8181                                             first.twoMachinesColor[0] :
8182                                             second.twoMachinesColor[0] ;
8183
8184                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8185                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8186                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8187                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8188                 } else
8189                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8190                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8191                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8192                 } else
8193                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8194                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8195                 }
8196
8197                 // now verify win claims, but not in drop games, as we don't understand those yet
8198                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8199                                                  || gameInfo.variant == VariantGreat) &&
8200                     (result == WhiteWins && claimer == 'w' ||
8201                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8202                       if (appData.debugMode) {
8203                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8204                                 result, epStatus[forwardMostMove], forwardMostMove);
8205                       }
8206                       if(result != trueResult) {
8207                               sprintf(buf, "False win claim: '%s'", resultDetails);
8208                               result = claimer == 'w' ? BlackWins : WhiteWins;
8209                               resultDetails = buf;
8210                       }
8211                 } else
8212                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8213                     && (forwardMostMove <= backwardMostMove ||
8214                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8215                         (claimer=='b')==(forwardMostMove&1))
8216                                                                                   ) {
8217                       /* [HGM] verify: draws that were not flagged are false claims */
8218                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8219                       result = claimer == 'w' ? BlackWins : WhiteWins;
8220                       resultDetails = buf;
8221                 }
8222                 /* (Claiming a loss is accepted no questions asked!) */
8223             }
8224
8225             /* [HGM] bare: don't allow bare King to win */
8226             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8227                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8228                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8229                && result != GameIsDrawn)
8230             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8231                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8232                         int p = (int)boards[forwardMostMove][i][j] - color;
8233                         if(p >= 0 && p <= (int)WhiteKing) k++;
8234                 }
8235                 if (appData.debugMode) {
8236                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8237                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8238                 }
8239                 if(k <= 1) {
8240                         result = GameIsDrawn;
8241                         sprintf(buf, "%s but bare king", resultDetails);
8242                         resultDetails = buf;
8243                 }
8244             }
8245         }
8246
8247         if(serverMoves != NULL && !loadFlag) { char c = '=';
8248             if(result==WhiteWins) c = '+';
8249             if(result==BlackWins) c = '-';
8250             if(resultDetails != NULL)
8251                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8252         }
8253         if (resultDetails != NULL) {
8254             gameInfo.result = result;
8255             gameInfo.resultDetails = StrSave(resultDetails);
8256
8257             /* display last move only if game was not loaded from file */
8258             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8259                 DisplayMove(currentMove - 1);
8260
8261             if (forwardMostMove != 0) {
8262                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8263                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8264                                                                 ) {
8265                     if (*appData.saveGameFile != NULLCHAR) {
8266                         SaveGameToFile(appData.saveGameFile, TRUE);
8267                     } else if (appData.autoSaveGames) {
8268                         AutoSaveGame();
8269                     }
8270                     if (*appData.savePositionFile != NULLCHAR) {
8271                         SavePositionToFile(appData.savePositionFile);
8272                     }
8273                 }
8274             }
8275
8276             /* Tell program how game ended in case it is learning */
8277             /* [HGM] Moved this to after saving the PGN, just in case */
8278             /* engine died and we got here through time loss. In that */
8279             /* case we will get a fatal error writing the pipe, which */
8280             /* would otherwise lose us the PGN.                       */
8281             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8282             /* output during GameEnds should never be fatal anymore   */
8283             if (gameMode == MachinePlaysWhite ||
8284                 gameMode == MachinePlaysBlack ||
8285                 gameMode == TwoMachinesPlay ||
8286                 gameMode == IcsPlayingWhite ||
8287                 gameMode == IcsPlayingBlack ||
8288                 gameMode == BeginningOfGame) {
8289                 char buf[MSG_SIZ];
8290                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8291                         resultDetails);
8292                 if (first.pr != NoProc) {
8293                     SendToProgram(buf, &first);
8294                 }
8295                 if (second.pr != NoProc &&
8296                     gameMode == TwoMachinesPlay) {
8297                     SendToProgram(buf, &second);
8298                 }
8299             }
8300         }
8301
8302         if (appData.icsActive) {
8303             if (appData.quietPlay &&
8304                 (gameMode == IcsPlayingWhite ||
8305                  gameMode == IcsPlayingBlack)) {
8306                 SendToICS(ics_prefix);
8307                 SendToICS("set shout 1\n");
8308             }
8309             nextGameMode = IcsIdle;
8310             ics_user_moved = FALSE;
8311             /* clean up premove.  It's ugly when the game has ended and the
8312              * premove highlights are still on the board.
8313              */
8314             if (gotPremove) {
8315               gotPremove = FALSE;
8316               ClearPremoveHighlights();
8317               DrawPosition(FALSE, boards[currentMove]);
8318             }
8319             if (whosays == GE_ICS) {
8320                 switch (result) {
8321                 case WhiteWins:
8322                     if (gameMode == IcsPlayingWhite)
8323                         PlayIcsWinSound();
8324                     else if(gameMode == IcsPlayingBlack)
8325                         PlayIcsLossSound();
8326                     break;
8327                 case BlackWins:
8328                     if (gameMode == IcsPlayingBlack)
8329                         PlayIcsWinSound();
8330                     else if(gameMode == IcsPlayingWhite)
8331                         PlayIcsLossSound();
8332                     break;
8333                 case GameIsDrawn:
8334                     PlayIcsDrawSound();
8335                     break;
8336                 default:
8337                     PlayIcsUnfinishedSound();
8338                 }
8339             }
8340         } else if (gameMode == EditGame ||
8341                    gameMode == PlayFromGameFile ||
8342                    gameMode == AnalyzeMode ||
8343                    gameMode == AnalyzeFile) {
8344             nextGameMode = gameMode;
8345         } else {
8346             nextGameMode = EndOfGame;
8347         }
8348         pausing = FALSE;
8349         ModeHighlight();
8350     } else {
8351         nextGameMode = gameMode;
8352     }
8353
8354     if (appData.noChessProgram) {
8355         gameMode = nextGameMode;
8356         ModeHighlight();
8357         endingGame = 0; /* [HGM] crash */
8358         return;
8359     }
8360
8361     if (first.reuse) {
8362         /* Put first chess program into idle state */
8363         if (first.pr != NoProc &&
8364             (gameMode == MachinePlaysWhite ||
8365              gameMode == MachinePlaysBlack ||
8366              gameMode == TwoMachinesPlay ||
8367              gameMode == IcsPlayingWhite ||
8368              gameMode == IcsPlayingBlack ||
8369              gameMode == BeginningOfGame)) {
8370             SendToProgram("force\n", &first);
8371             if (first.usePing) {
8372               char buf[MSG_SIZ];
8373               sprintf(buf, "ping %d\n", ++first.lastPing);
8374               SendToProgram(buf, &first);
8375             }
8376         }
8377     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8378         /* Kill off first chess program */
8379         if (first.isr != NULL)
8380           RemoveInputSource(first.isr);
8381         first.isr = NULL;
8382
8383         if (first.pr != NoProc) {
8384             ExitAnalyzeMode();
8385             DoSleep( appData.delayBeforeQuit );
8386             SendToProgram("quit\n", &first);
8387             DoSleep( appData.delayAfterQuit );
8388             DestroyChildProcess(first.pr, first.useSigterm);
8389         }
8390         first.pr = NoProc;
8391     }
8392     if (second.reuse) {
8393         /* Put second chess program into idle state */
8394         if (second.pr != NoProc &&
8395             gameMode == TwoMachinesPlay) {
8396             SendToProgram("force\n", &second);
8397             if (second.usePing) {
8398               char buf[MSG_SIZ];
8399               sprintf(buf, "ping %d\n", ++second.lastPing);
8400               SendToProgram(buf, &second);
8401             }
8402         }
8403     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8404         /* Kill off second chess program */
8405         if (second.isr != NULL)
8406           RemoveInputSource(second.isr);
8407         second.isr = NULL;
8408
8409         if (second.pr != NoProc) {
8410             DoSleep( appData.delayBeforeQuit );
8411             SendToProgram("quit\n", &second);
8412             DoSleep( appData.delayAfterQuit );
8413             DestroyChildProcess(second.pr, second.useSigterm);
8414         }
8415         second.pr = NoProc;
8416     }
8417
8418     if (matchMode && gameMode == TwoMachinesPlay) {
8419         switch (result) {
8420         case WhiteWins:
8421           if (first.twoMachinesColor[0] == 'w') {
8422             first.matchWins++;
8423           } else {
8424             second.matchWins++;
8425           }
8426           break;
8427         case BlackWins:
8428           if (first.twoMachinesColor[0] == 'b') {
8429             first.matchWins++;
8430           } else {
8431             second.matchWins++;
8432           }
8433           break;
8434         default:
8435           break;
8436         }
8437         if (matchGame < appData.matchGames) {
8438             char *tmp;
8439             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8440                 tmp = first.twoMachinesColor;
8441                 first.twoMachinesColor = second.twoMachinesColor;
8442                 second.twoMachinesColor = tmp;
8443             }
8444             gameMode = nextGameMode;
8445             matchGame++;
8446             if(appData.matchPause>10000 || appData.matchPause<10)
8447                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8448             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8449             endingGame = 0; /* [HGM] crash */
8450             return;
8451         } else {
8452             char buf[MSG_SIZ];
8453             gameMode = nextGameMode;
8454             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8455                     first.tidy, second.tidy,
8456                     first.matchWins, second.matchWins,
8457                     appData.matchGames - (first.matchWins + second.matchWins));
8458             DisplayFatalError(buf, 0, 0);
8459         }
8460     }
8461     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8462         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8463       ExitAnalyzeMode();
8464     gameMode = nextGameMode;
8465     ModeHighlight();
8466     endingGame = 0;  /* [HGM] crash */
8467 }
8468
8469 /* Assumes program was just initialized (initString sent).
8470    Leaves program in force mode. */
8471 void
8472 FeedMovesToProgram(cps, upto)
8473      ChessProgramState *cps;
8474      int upto;
8475 {
8476     int i;
8477
8478     if (appData.debugMode)
8479       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8480               startedFromSetupPosition ? "position and " : "",
8481               backwardMostMove, upto, cps->which);
8482     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8483         // [HGM] variantswitch: make engine aware of new variant
8484         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8485                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8486         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8487         SendToProgram(buf, cps);
8488         currentlyInitializedVariant = gameInfo.variant;
8489     }
8490     SendToProgram("force\n", cps);
8491     if (startedFromSetupPosition) {
8492         SendBoard(cps, backwardMostMove);
8493     if (appData.debugMode) {
8494         fprintf(debugFP, "feedMoves\n");
8495     }
8496     }
8497     for (i = backwardMostMove; i < upto; i++) {
8498         SendMoveToProgram(i, cps);
8499     }
8500 }
8501
8502
8503 void
8504 ResurrectChessProgram()
8505 {
8506      /* The chess program may have exited.
8507         If so, restart it and feed it all the moves made so far. */
8508
8509     if (appData.noChessProgram || first.pr != NoProc) return;
8510
8511     StartChessProgram(&first);
8512     InitChessProgram(&first, FALSE);
8513     FeedMovesToProgram(&first, currentMove);
8514
8515     if (!first.sendTime) {
8516         /* can't tell gnuchess what its clock should read,
8517            so we bow to its notion. */
8518         ResetClocks();
8519         timeRemaining[0][currentMove] = whiteTimeRemaining;
8520         timeRemaining[1][currentMove] = blackTimeRemaining;
8521     }
8522
8523     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8524                 appData.icsEngineAnalyze) && first.analysisSupport) {
8525       SendToProgram("analyze\n", &first);
8526       first.analyzing = TRUE;
8527     }
8528 }
8529
8530 /*
8531  * Button procedures
8532  */
8533 void
8534 Reset(redraw, init)
8535      int redraw, init;
8536 {
8537     int i;
8538
8539     if (appData.debugMode) {
8540         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8541                 redraw, init, gameMode);
8542     }
8543     pausing = pauseExamInvalid = FALSE;
8544     startedFromSetupPosition = blackPlaysFirst = FALSE;
8545     firstMove = TRUE;
8546     whiteFlag = blackFlag = FALSE;
8547     userOfferedDraw = FALSE;
8548     hintRequested = bookRequested = FALSE;
8549     first.maybeThinking = FALSE;
8550     second.maybeThinking = FALSE;
8551     first.bookSuspend = FALSE; // [HGM] book
8552     second.bookSuspend = FALSE;
8553     thinkOutput[0] = NULLCHAR;
8554     lastHint[0] = NULLCHAR;
8555     ClearGameInfo(&gameInfo);
8556     gameInfo.variant = StringToVariant(appData.variant);
8557     ics_user_moved = ics_clock_paused = FALSE;
8558     ics_getting_history = H_FALSE;
8559     ics_gamenum = -1;
8560     white_holding[0] = black_holding[0] = NULLCHAR;
8561     ClearProgramStats();
8562     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8563
8564     ResetFrontEnd();
8565     ClearHighlights();
8566     flipView = appData.flipView;
8567     ClearPremoveHighlights();
8568     gotPremove = FALSE;
8569     alarmSounded = FALSE;
8570
8571     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8572     if(appData.serverMovesName != NULL) {
8573         /* [HGM] prepare to make moves file for broadcasting */
8574         clock_t t = clock();
8575         if(serverMoves != NULL) fclose(serverMoves);
8576         serverMoves = fopen(appData.serverMovesName, "r");
8577         if(serverMoves != NULL) {
8578             fclose(serverMoves);
8579             /* delay 15 sec before overwriting, so all clients can see end */
8580             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8581         }
8582         serverMoves = fopen(appData.serverMovesName, "w");
8583     }
8584
8585     ExitAnalyzeMode();
8586     gameMode = BeginningOfGame;
8587     ModeHighlight();
8588
8589     if(appData.icsActive) gameInfo.variant = VariantNormal;
8590     currentMove = forwardMostMove = backwardMostMove = 0;
8591     InitPosition(redraw);
8592     for (i = 0; i < MAX_MOVES; i++) {
8593         if (commentList[i] != NULL) {
8594             free(commentList[i]);
8595             commentList[i] = NULL;
8596         }
8597     }
8598
8599     ResetClocks();
8600     timeRemaining[0][0] = whiteTimeRemaining;
8601     timeRemaining[1][0] = blackTimeRemaining;
8602     if (first.pr == NULL) {
8603         StartChessProgram(&first);
8604     }
8605     if (init) {
8606             InitChessProgram(&first, startedFromSetupPosition);
8607     }
8608
8609     DisplayTitle("");
8610     DisplayMessage("", "");
8611     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8612     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8613     return;
8614 }
8615
8616 void
8617 AutoPlayGameLoop()
8618 {
8619     for (;;) {
8620         if (!AutoPlayOneMove())
8621           return;
8622         if (matchMode || appData.timeDelay == 0)
8623           continue;
8624         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8625           return;
8626         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8627         break;
8628     }
8629 }
8630
8631
8632 int
8633 AutoPlayOneMove()
8634 {
8635     int fromX, fromY, toX, toY;
8636
8637     if (appData.debugMode) {
8638       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8639     }
8640
8641     if (gameMode != PlayFromGameFile)
8642       return FALSE;
8643
8644     if (currentMove >= forwardMostMove) {
8645       gameMode = EditGame;
8646       ModeHighlight();
8647
8648       /* [AS] Clear current move marker at the end of a game */
8649       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8650
8651       return FALSE;
8652     }
8653
8654     toX = moveList[currentMove][2] - AAA;
8655     toY = moveList[currentMove][3] - ONE;
8656
8657     if (moveList[currentMove][1] == '@') {
8658         if (appData.highlightLastMove) {
8659             SetHighlights(-1, -1, toX, toY);
8660         }
8661     } else {
8662         fromX = moveList[currentMove][0] - AAA;
8663         fromY = moveList[currentMove][1] - ONE;
8664
8665         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8666
8667         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8668
8669         if (appData.highlightLastMove) {
8670             SetHighlights(fromX, fromY, toX, toY);
8671         }
8672     }
8673     DisplayMove(currentMove);
8674     SendMoveToProgram(currentMove++, &first);
8675     DisplayBothClocks();
8676     DrawPosition(FALSE, boards[currentMove]);
8677     // [HGM] PV info: always display, routine tests if empty
8678     DisplayComment(currentMove - 1, commentList[currentMove]);
8679     return TRUE;
8680 }
8681
8682
8683 int
8684 LoadGameOneMove(readAhead)
8685      ChessMove readAhead;
8686 {
8687     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8688     char promoChar = NULLCHAR;
8689     ChessMove moveType;
8690     char move[MSG_SIZ];
8691     char *p, *q;
8692
8693     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8694         gameMode != AnalyzeMode && gameMode != Training) {
8695         gameFileFP = NULL;
8696         return FALSE;
8697     }
8698
8699     yyboardindex = forwardMostMove;
8700     if (readAhead != (ChessMove)0) {
8701       moveType = readAhead;
8702     } else {
8703       if (gameFileFP == NULL)
8704           return FALSE;
8705       moveType = (ChessMove) yylex();
8706     }
8707
8708     done = FALSE;
8709     switch (moveType) {
8710       case Comment:
8711         if (appData.debugMode)
8712           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8713         p = yy_text;
8714         if (*p == '{' || *p == '[' || *p == '(') {
8715             p[strlen(p) - 1] = NULLCHAR;
8716             p++;
8717         }
8718
8719         /* append the comment but don't display it */
8720         while (*p == '\n') p++;
8721         AppendComment(currentMove, p);
8722         return TRUE;
8723
8724       case WhiteCapturesEnPassant:
8725       case BlackCapturesEnPassant:
8726       case WhitePromotionChancellor:
8727       case BlackPromotionChancellor:
8728       case WhitePromotionArchbishop:
8729       case BlackPromotionArchbishop:
8730       case WhitePromotionCentaur:
8731       case BlackPromotionCentaur:
8732       case WhitePromotionQueen:
8733       case BlackPromotionQueen:
8734       case WhitePromotionRook:
8735       case BlackPromotionRook:
8736       case WhitePromotionBishop:
8737       case BlackPromotionBishop:
8738       case WhitePromotionKnight:
8739       case BlackPromotionKnight:
8740       case WhitePromotionKing:
8741       case BlackPromotionKing:
8742       case NormalMove:
8743       case WhiteKingSideCastle:
8744       case WhiteQueenSideCastle:
8745       case BlackKingSideCastle:
8746       case BlackQueenSideCastle:
8747       case WhiteKingSideCastleWild:
8748       case WhiteQueenSideCastleWild:
8749       case BlackKingSideCastleWild:
8750       case BlackQueenSideCastleWild:
8751       /* PUSH Fabien */
8752       case WhiteHSideCastleFR:
8753       case WhiteASideCastleFR:
8754       case BlackHSideCastleFR:
8755       case BlackASideCastleFR:
8756       /* POP Fabien */
8757         if (appData.debugMode)
8758           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8759         fromX = currentMoveString[0] - AAA;
8760         fromY = currentMoveString[1] - ONE;
8761         toX = currentMoveString[2] - AAA;
8762         toY = currentMoveString[3] - ONE;
8763         promoChar = currentMoveString[4];
8764         break;
8765
8766       case WhiteDrop:
8767       case BlackDrop:
8768         if (appData.debugMode)
8769           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8770         fromX = moveType == WhiteDrop ?
8771           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8772         (int) CharToPiece(ToLower(currentMoveString[0]));
8773         fromY = DROP_RANK;
8774         toX = currentMoveString[2] - AAA;
8775         toY = currentMoveString[3] - ONE;
8776         break;
8777
8778       case WhiteWins:
8779       case BlackWins:
8780       case GameIsDrawn:
8781       case GameUnfinished:
8782         if (appData.debugMode)
8783           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8784         p = strchr(yy_text, '{');
8785         if (p == NULL) p = strchr(yy_text, '(');
8786         if (p == NULL) {
8787             p = yy_text;
8788             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8789         } else {
8790             q = strchr(p, *p == '{' ? '}' : ')');
8791             if (q != NULL) *q = NULLCHAR;
8792             p++;
8793         }
8794         GameEnds(moveType, p, GE_FILE);
8795         done = TRUE;
8796         if (cmailMsgLoaded) {
8797             ClearHighlights();
8798             flipView = WhiteOnMove(currentMove);
8799             if (moveType == GameUnfinished) flipView = !flipView;
8800             if (appData.debugMode)
8801               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8802         }
8803         break;
8804
8805       case (ChessMove) 0:       /* end of file */
8806         if (appData.debugMode)
8807           fprintf(debugFP, "Parser hit end of file\n");
8808         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8809                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8810           case MT_NONE:
8811           case MT_CHECK:
8812             break;
8813           case MT_CHECKMATE:
8814           case MT_STAINMATE:
8815             if (WhiteOnMove(currentMove)) {
8816                 GameEnds(BlackWins, "Black mates", GE_FILE);
8817             } else {
8818                 GameEnds(WhiteWins, "White mates", GE_FILE);
8819             }
8820             break;
8821           case MT_STALEMATE:
8822             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8823             break;
8824         }
8825         done = TRUE;
8826         break;
8827
8828       case MoveNumberOne:
8829         if (lastLoadGameStart == GNUChessGame) {
8830             /* GNUChessGames have numbers, but they aren't move numbers */
8831             if (appData.debugMode)
8832               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8833                       yy_text, (int) moveType);
8834             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8835         }
8836         /* else fall thru */
8837
8838       case XBoardGame:
8839       case GNUChessGame:
8840       case PGNTag:
8841         /* Reached start of next game in file */
8842         if (appData.debugMode)
8843           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8844         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8845                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8846           case MT_NONE:
8847           case MT_CHECK:
8848             break;
8849           case MT_CHECKMATE:
8850           case MT_STAINMATE:
8851             if (WhiteOnMove(currentMove)) {
8852                 GameEnds(BlackWins, "Black mates", GE_FILE);
8853             } else {
8854                 GameEnds(WhiteWins, "White mates", GE_FILE);
8855             }
8856             break;
8857           case MT_STALEMATE:
8858             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8859             break;
8860         }
8861         done = TRUE;
8862         break;
8863
8864       case PositionDiagram:     /* should not happen; ignore */
8865       case ElapsedTime:         /* ignore */
8866       case NAG:                 /* ignore */
8867         if (appData.debugMode)
8868           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8869                   yy_text, (int) moveType);
8870         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8871
8872       case IllegalMove:
8873         if (appData.testLegality) {
8874             if (appData.debugMode)
8875               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8876             sprintf(move, _("Illegal move: %d.%s%s"),
8877                     (forwardMostMove / 2) + 1,
8878                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8879             DisplayError(move, 0);
8880             done = TRUE;
8881         } else {
8882             if (appData.debugMode)
8883               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8884                       yy_text, currentMoveString);
8885             fromX = currentMoveString[0] - AAA;
8886             fromY = currentMoveString[1] - ONE;
8887             toX = currentMoveString[2] - AAA;
8888             toY = currentMoveString[3] - ONE;
8889             promoChar = currentMoveString[4];
8890         }
8891         break;
8892
8893       case AmbiguousMove:
8894         if (appData.debugMode)
8895           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8896         sprintf(move, _("Ambiguous move: %d.%s%s"),
8897                 (forwardMostMove / 2) + 1,
8898                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8899         DisplayError(move, 0);
8900         done = TRUE;
8901         break;
8902
8903       default:
8904       case ImpossibleMove:
8905         if (appData.debugMode)
8906           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8907         sprintf(move, _("Illegal move: %d.%s%s"),
8908                 (forwardMostMove / 2) + 1,
8909                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8910         DisplayError(move, 0);
8911         done = TRUE;
8912         break;
8913     }
8914
8915     if (done) {
8916         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8917             DrawPosition(FALSE, boards[currentMove]);
8918             DisplayBothClocks();
8919             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8920               DisplayComment(currentMove - 1, commentList[currentMove]);
8921         }
8922         (void) StopLoadGameTimer();
8923         gameFileFP = NULL;
8924         cmailOldMove = forwardMostMove;
8925         return FALSE;
8926     } else {
8927         /* currentMoveString is set as a side-effect of yylex */
8928         strcat(currentMoveString, "\n");
8929         strcpy(moveList[forwardMostMove], currentMoveString);
8930
8931         thinkOutput[0] = NULLCHAR;
8932         MakeMove(fromX, fromY, toX, toY, promoChar);
8933         currentMove = forwardMostMove;
8934         return TRUE;
8935     }
8936 }
8937
8938 /* Load the nth game from the given file */
8939 int
8940 LoadGameFromFile(filename, n, title, useList)
8941      char *filename;
8942      int n;
8943      char *title;
8944      /*Boolean*/ int useList;
8945 {
8946     FILE *f;
8947     char buf[MSG_SIZ];
8948
8949     if (strcmp(filename, "-") == 0) {
8950         f = stdin;
8951         title = "stdin";
8952     } else {
8953         f = fopen(filename, "rb");
8954         if (f == NULL) {
8955           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8956             DisplayError(buf, errno);
8957             return FALSE;
8958         }
8959     }
8960     if (fseek(f, 0, 0) == -1) {
8961         /* f is not seekable; probably a pipe */
8962         useList = FALSE;
8963     }
8964     if (useList && n == 0) {
8965         int error = GameListBuild(f);
8966         if (error) {
8967             DisplayError(_("Cannot build game list"), error);
8968         } else if (!ListEmpty(&gameList) &&
8969                    ((ListGame *) gameList.tailPred)->number > 1) {
8970           // TODO convert to GTK
8971           //        GameListPopUp(f, title);
8972             return TRUE;
8973         }
8974         GameListDestroy();
8975         n = 1;
8976     }
8977     if (n == 0) n = 1;
8978     return LoadGame(f, n, title, FALSE);
8979 }
8980
8981
8982 void
8983 MakeRegisteredMove()
8984 {
8985     int fromX, fromY, toX, toY;
8986     char promoChar;
8987     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8988         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8989           case CMAIL_MOVE:
8990           case CMAIL_DRAW:
8991             if (appData.debugMode)
8992               fprintf(debugFP, "Restoring %s for game %d\n",
8993                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8994
8995             thinkOutput[0] = NULLCHAR;
8996             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8997             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8998             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8999             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9000             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9001             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9002             MakeMove(fromX, fromY, toX, toY, promoChar);
9003             ShowMove(fromX, fromY, toX, toY);
9004
9005             switch (MateTest(boards[currentMove], PosFlags(currentMove),
9006                              EP_UNKNOWN, castlingRights[currentMove]) ) {
9007               case MT_NONE:
9008               case MT_CHECK:
9009                 break;
9010
9011               case MT_CHECKMATE:
9012               case MT_STAINMATE:
9013                 if (WhiteOnMove(currentMove)) {
9014                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9015                 } else {
9016                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9017                 }
9018                 break;
9019
9020               case MT_STALEMATE:
9021                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9022                 break;
9023             }
9024
9025             break;
9026
9027           case CMAIL_RESIGN:
9028             if (WhiteOnMove(currentMove)) {
9029                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9030             } else {
9031                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9032             }
9033             break;
9034
9035           case CMAIL_ACCEPT:
9036             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9037             break;
9038
9039           default:
9040             break;
9041         }
9042     }
9043
9044     return;
9045 }
9046
9047 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9048 int
9049 CmailLoadGame(f, gameNumber, title, useList)
9050      FILE *f;
9051      int gameNumber;
9052      char *title;
9053      int useList;
9054 {
9055     int retVal;
9056
9057     if (gameNumber > nCmailGames) {
9058         DisplayError(_("No more games in this message"), 0);
9059         return FALSE;
9060     }
9061     if (f == lastLoadGameFP) {
9062         int offset = gameNumber - lastLoadGameNumber;
9063         if (offset == 0) {
9064             cmailMsg[0] = NULLCHAR;
9065             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9066                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9067                 nCmailMovesRegistered--;
9068             }
9069             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9070             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9071                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9072             }
9073         } else {
9074             if (! RegisterMove()) return FALSE;
9075         }
9076     }
9077
9078     retVal = LoadGame(f, gameNumber, title, useList);
9079
9080     /* Make move registered during previous look at this game, if any */
9081     MakeRegisteredMove();
9082
9083     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9084         commentList[currentMove]
9085           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9086         DisplayComment(currentMove - 1, commentList[currentMove]);
9087     }
9088
9089     return retVal;
9090 }
9091
9092 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9093 int
9094 ReloadGame(offset)
9095      int offset;
9096 {
9097     int gameNumber = lastLoadGameNumber + offset;
9098     if (lastLoadGameFP == NULL) {
9099         DisplayError(_("No game has been loaded yet"), 0);
9100         return FALSE;
9101     }
9102     if (gameNumber <= 0) {
9103         DisplayError(_("Can't back up any further"), 0);
9104         return FALSE;
9105     }
9106     if (cmailMsgLoaded) {
9107         return CmailLoadGame(lastLoadGameFP, gameNumber,
9108                              lastLoadGameTitle, lastLoadGameUseList);
9109     } else {
9110         return LoadGame(lastLoadGameFP, gameNumber,
9111                         lastLoadGameTitle, lastLoadGameUseList);
9112     }
9113 }
9114
9115
9116
9117 /* Load the nth game from open file f */
9118 int
9119 LoadGame(f, gameNumber, title, useList)
9120      FILE *f;
9121      int gameNumber;
9122      char *title;
9123      int useList;
9124 {
9125     ChessMove cm;
9126     char buf[MSG_SIZ];
9127     int gn = gameNumber;
9128     ListGame *lg = NULL;
9129     int numPGNTags = 0;
9130     int err;
9131     GameMode oldGameMode;
9132     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9133
9134     if (appData.debugMode)
9135         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9136
9137     if (gameMode == Training )
9138         SetTrainingModeOff();
9139
9140     oldGameMode = gameMode;
9141     if (gameMode != BeginningOfGame) 
9142       {
9143         Reset(FALSE, TRUE);
9144       };
9145
9146     gameFileFP = f;
9147     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9148       {
9149         fclose(lastLoadGameFP);
9150       };
9151
9152     if (useList) 
9153       {
9154         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9155         
9156         if (lg) 
9157           {
9158             fseek(f, lg->offset, 0);
9159             GameListHighlight(gameNumber);
9160             gn = 1;
9161           }
9162         else 
9163           {
9164             DisplayError(_("Game number out of range"), 0);
9165             return FALSE;
9166           };
9167       } 
9168     else 
9169       {
9170         GameListDestroy();
9171         if (fseek(f, 0, 0) == -1) 
9172           {
9173             if (f == lastLoadGameFP ?
9174                 gameNumber == lastLoadGameNumber + 1 :
9175                 gameNumber == 1) 
9176               {
9177                 gn = 1;
9178               } 
9179             else 
9180               {
9181                 DisplayError(_("Can't seek on game file"), 0);
9182                 return FALSE;
9183               };
9184           };
9185       };
9186
9187     lastLoadGameFP      = f;
9188     lastLoadGameNumber  = gameNumber;
9189     strcpy(lastLoadGameTitle, title);
9190     lastLoadGameUseList = useList;
9191
9192     yynewfile(f);
9193
9194     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9195       {
9196         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9197                  lg->gameInfo.black);
9198         DisplayTitle(buf);
9199       } 
9200     else if (*title != NULLCHAR) 
9201       {
9202         if (gameNumber > 1) 
9203           {
9204             sprintf(buf, "%s %d", title, gameNumber);
9205             DisplayTitle(buf);
9206           } 
9207         else 
9208           {
9209             DisplayTitle(title);
9210           };
9211       };
9212
9213     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9214       {
9215         gameMode = PlayFromGameFile;
9216         ModeHighlight();
9217       };
9218
9219     currentMove = forwardMostMove = backwardMostMove = 0;
9220     CopyBoard(boards[0], initialPosition);
9221     StopClocks();
9222
9223     /*
9224      * Skip the first gn-1 games in the file.
9225      * Also skip over anything that precedes an identifiable
9226      * start of game marker, to avoid being confused by
9227      * garbage at the start of the file.  Currently
9228      * recognized start of game markers are the move number "1",
9229      * the pattern "gnuchess .* game", the pattern
9230      * "^[#;%] [^ ]* game file", and a PGN tag block.
9231      * A game that starts with one of the latter two patterns
9232      * will also have a move number 1, possibly
9233      * following a position diagram.
9234      * 5-4-02: Let's try being more lenient and allowing a game to
9235      * start with an unnumbered move.  Does that break anything?
9236      */
9237     cm = lastLoadGameStart = (ChessMove) 0;
9238     while (gn > 0) {
9239         yyboardindex = forwardMostMove;
9240         cm = (ChessMove) yylex();
9241         switch (cm) {
9242           case (ChessMove) 0:
9243             if (cmailMsgLoaded) {
9244                 nCmailGames = CMAIL_MAX_GAMES - gn;
9245             } else {
9246                 Reset(TRUE, TRUE);
9247                 DisplayError(_("Game not found in file"), 0);
9248             }
9249             return FALSE;
9250
9251           case GNUChessGame:
9252           case XBoardGame:
9253             gn--;
9254             lastLoadGameStart = cm;
9255             break;
9256
9257           case MoveNumberOne:
9258             switch (lastLoadGameStart) {
9259               case GNUChessGame:
9260               case XBoardGame:
9261               case PGNTag:
9262                 break;
9263               case MoveNumberOne:
9264               case (ChessMove) 0:
9265                 gn--;           /* count this game */
9266                 lastLoadGameStart = cm;
9267                 break;
9268               default:
9269                 /* impossible */
9270                 break;
9271             }
9272             break;
9273
9274           case PGNTag:
9275             switch (lastLoadGameStart) {
9276               case GNUChessGame:
9277               case PGNTag:
9278               case MoveNumberOne:
9279               case (ChessMove) 0:
9280                 gn--;           /* count this game */
9281                 lastLoadGameStart = cm;
9282                 break;
9283               case XBoardGame:
9284                 lastLoadGameStart = cm; /* game counted already */
9285                 break;
9286               default:
9287                 /* impossible */
9288                 break;
9289             }
9290             if (gn > 0) {
9291                 do {
9292                     yyboardindex = forwardMostMove;
9293                     cm = (ChessMove) yylex();
9294                 } while (cm == PGNTag || cm == Comment);
9295             }
9296             break;
9297
9298           case WhiteWins:
9299           case BlackWins:
9300           case GameIsDrawn:
9301             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9302                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9303                     != CMAIL_OLD_RESULT) {
9304                     nCmailResults ++ ;
9305                     cmailResult[  CMAIL_MAX_GAMES
9306                                 - gn - 1] = CMAIL_OLD_RESULT;
9307                 }
9308             }
9309             break;
9310
9311           case NormalMove:
9312             /* Only a NormalMove can be at the start of a game
9313              * without a position diagram. */
9314             if (lastLoadGameStart == (ChessMove) 0) {
9315               gn--;
9316               lastLoadGameStart = MoveNumberOne;
9317             }
9318             break;
9319
9320           default:
9321             break;
9322         }
9323     }
9324
9325     if (appData.debugMode)
9326       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9327
9328     if (cm == XBoardGame) {
9329         /* Skip any header junk before position diagram and/or move 1 */
9330         for (;;) {
9331             yyboardindex = forwardMostMove;
9332             cm = (ChessMove) yylex();
9333
9334             if (cm == (ChessMove) 0 ||
9335                 cm == GNUChessGame || cm == XBoardGame) {
9336                 /* Empty game; pretend end-of-file and handle later */
9337                 cm = (ChessMove) 0;
9338                 break;
9339             }
9340
9341             if (cm == MoveNumberOne || cm == PositionDiagram ||
9342                 cm == PGNTag || cm == Comment)
9343               break;
9344         }
9345     } else if (cm == GNUChessGame) {
9346         if (gameInfo.event != NULL) {
9347             free(gameInfo.event);
9348         }
9349         gameInfo.event = StrSave(yy_text);
9350     }
9351
9352     startedFromSetupPosition = FALSE;
9353     while (cm == PGNTag) {
9354         if (appData.debugMode)
9355           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9356         err = ParsePGNTag(yy_text, &gameInfo);
9357         if (!err) numPGNTags++;
9358
9359         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9360         if(gameInfo.variant != oldVariant) {
9361             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9362             InitPosition(TRUE);
9363             oldVariant = gameInfo.variant;
9364             if (appData.debugMode)
9365               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9366         }
9367
9368
9369         if (gameInfo.fen != NULL) {
9370           Board initial_position;
9371           startedFromSetupPosition = TRUE;
9372           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9373             Reset(TRUE, TRUE);
9374             DisplayError(_("Bad FEN position in file"), 0);
9375             return FALSE;
9376           }
9377           CopyBoard(boards[0], initial_position);
9378           if (blackPlaysFirst) {
9379             currentMove = forwardMostMove = backwardMostMove = 1;
9380             CopyBoard(boards[1], initial_position);
9381             strcpy(moveList[0], "");
9382             strcpy(parseList[0], "");
9383             timeRemaining[0][1] = whiteTimeRemaining;
9384             timeRemaining[1][1] = blackTimeRemaining;
9385             if (commentList[0] != NULL) {
9386               commentList[1] = commentList[0];
9387               commentList[0] = NULL;
9388             }
9389           } else {
9390             currentMove = forwardMostMove = backwardMostMove = 0;
9391           }
9392           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9393           {   int i;
9394               initialRulePlies = FENrulePlies;
9395               epStatus[forwardMostMove] = FENepStatus;
9396               for( i=0; i< nrCastlingRights; i++ )
9397                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9398           }
9399           yyboardindex = forwardMostMove;
9400           free(gameInfo.fen);
9401           gameInfo.fen = NULL;
9402         }
9403
9404         yyboardindex = forwardMostMove;
9405         cm = (ChessMove) yylex();
9406
9407         /* Handle comments interspersed among the tags */
9408         while (cm == Comment) {
9409             char *p;
9410             if (appData.debugMode)
9411               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9412             p = yy_text;
9413             if (*p == '{' || *p == '[' || *p == '(') {
9414                 p[strlen(p) - 1] = NULLCHAR;
9415                 p++;
9416             }
9417             while (*p == '\n') p++;
9418             AppendComment(currentMove, p);
9419             yyboardindex = forwardMostMove;
9420             cm = (ChessMove) yylex();
9421         }
9422     }
9423
9424     /* don't rely on existence of Event tag since if game was
9425      * pasted from clipboard the Event tag may not exist
9426      */
9427     if (numPGNTags > 0){
9428         char *tags;
9429         if (gameInfo.variant == VariantNormal) {
9430           gameInfo.variant = StringToVariant(gameInfo.event);
9431         }
9432         if (!matchMode) {
9433           if( appData.autoDisplayTags ) {
9434             tags = PGNTags(&gameInfo);
9435             TagsPopUp(tags, CmailMsg());
9436             free(tags);
9437           }
9438         }
9439     } else {
9440         /* Make something up, but don't display it now */
9441         SetGameInfo();
9442         TagsPopDown();
9443     }
9444
9445     if (cm == PositionDiagram) {
9446         int i, j;
9447         char *p;
9448         Board initial_position;
9449
9450         if (appData.debugMode)
9451           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9452
9453         if (!startedFromSetupPosition) {
9454             p = yy_text;
9455             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9456               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9457                 switch (*p) {
9458                   case '[':
9459                   case '-':
9460                   case ' ':
9461                   case '\t':
9462                   case '\n':
9463                   case '\r':
9464                     break;
9465                   default:
9466                     initial_position[i][j++] = CharToPiece(*p);
9467                     break;
9468                 }
9469             while (*p == ' ' || *p == '\t' ||
9470                    *p == '\n' || *p == '\r') p++;
9471
9472             if (strncmp(p, "black", strlen("black"))==0)
9473               blackPlaysFirst = TRUE;
9474             else
9475               blackPlaysFirst = FALSE;
9476             startedFromSetupPosition = TRUE;
9477
9478             CopyBoard(boards[0], initial_position);
9479             if (blackPlaysFirst) {
9480                 currentMove = forwardMostMove = backwardMostMove = 1;
9481                 CopyBoard(boards[1], initial_position);
9482                 strcpy(moveList[0], "");
9483                 strcpy(parseList[0], "");
9484                 timeRemaining[0][1] = whiteTimeRemaining;
9485                 timeRemaining[1][1] = blackTimeRemaining;
9486                 if (commentList[0] != NULL) {
9487                     commentList[1] = commentList[0];
9488                     commentList[0] = NULL;
9489                 }
9490             } else {
9491                 currentMove = forwardMostMove = backwardMostMove = 0;
9492             }
9493         }
9494         yyboardindex = forwardMostMove;
9495         cm = (ChessMove) yylex();
9496     }
9497
9498     if (first.pr == NoProc) {
9499         StartChessProgram(&first);
9500     }
9501     InitChessProgram(&first, FALSE);
9502     SendToProgram("force\n", &first);
9503     if (startedFromSetupPosition) {
9504         SendBoard(&first, forwardMostMove);
9505     if (appData.debugMode) {
9506         fprintf(debugFP, "Load Game\n");
9507     }
9508         DisplayBothClocks();
9509     }
9510
9511     /* [HGM] server: flag to write setup moves in broadcast file as one */
9512     loadFlag = appData.suppressLoadMoves;
9513
9514     while (cm == Comment) {
9515         char *p;
9516         if (appData.debugMode)
9517           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9518         p = yy_text;
9519         if (*p == '{' || *p == '[' || *p == '(') {
9520             p[strlen(p) - 1] = NULLCHAR;
9521             p++;
9522         }
9523         while (*p == '\n') p++;
9524         AppendComment(currentMove, p);
9525         yyboardindex = forwardMostMove;
9526         cm = (ChessMove) yylex();
9527     }
9528
9529     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9530         cm == WhiteWins || cm == BlackWins ||
9531         cm == GameIsDrawn || cm == GameUnfinished) {
9532         DisplayMessage("", _("No moves in game"));
9533         if (cmailMsgLoaded) {
9534             if (appData.debugMode)
9535               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9536             ClearHighlights();
9537             flipView = FALSE;
9538         }
9539         DrawPosition(FALSE, boards[currentMove]);
9540         DisplayBothClocks();
9541         gameMode = EditGame;
9542         ModeHighlight();
9543         gameFileFP = NULL;
9544         cmailOldMove = 0;
9545         return TRUE;
9546     }
9547
9548     // [HGM] PV info: routine tests if comment empty
9549     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9550         DisplayComment(currentMove - 1, commentList[currentMove]);
9551     }
9552     if (!matchMode && appData.timeDelay != 0)
9553       DrawPosition(FALSE, boards[currentMove]);
9554
9555     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9556       programStats.ok_to_send = 1;
9557     }
9558
9559     /* if the first token after the PGN tags is a move
9560      * and not move number 1, retrieve it from the parser
9561      */
9562     if (cm != MoveNumberOne)
9563         LoadGameOneMove(cm);
9564
9565     /* load the remaining moves from the file */
9566     while (LoadGameOneMove((ChessMove)0)) {
9567       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9568       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9569     }
9570
9571     /* rewind to the start of the game */
9572     currentMove = backwardMostMove;
9573
9574     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9575
9576     if (oldGameMode == AnalyzeFile ||
9577         oldGameMode == AnalyzeMode) {
9578       AnalyzeFileEvent();
9579     }
9580
9581     if (matchMode || appData.timeDelay == 0) {
9582       ToEndEvent();
9583       gameMode = EditGame;
9584       ModeHighlight();
9585     } else if (appData.timeDelay > 0) {
9586       AutoPlayGameLoop();
9587     }
9588
9589     if (appData.debugMode)
9590         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9591
9592     loadFlag = 0; /* [HGM] true game starts */
9593     return TRUE;
9594 }
9595
9596 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9597 int
9598 ReloadPosition(offset)
9599      int offset;
9600 {
9601     int positionNumber = lastLoadPositionNumber + offset;
9602     if (lastLoadPositionFP == NULL) {
9603         DisplayError(_("No position has been loaded yet"), 0);
9604         return FALSE;
9605     }
9606     if (positionNumber <= 0) {
9607         DisplayError(_("Can't back up any further"), 0);
9608         return FALSE;
9609     }
9610     return LoadPosition(lastLoadPositionFP, positionNumber,
9611                         lastLoadPositionTitle);
9612 }
9613
9614 /* Load the nth position from the given file */
9615 int
9616 LoadPositionFromFile(filename, n, title)
9617      char *filename;
9618      int n;
9619      char *title;
9620 {
9621     FILE *f;
9622     char buf[MSG_SIZ];
9623
9624     if (strcmp(filename, "-") == 0) {
9625         return LoadPosition(stdin, n, "stdin");
9626     } else {
9627         f = fopen(filename, "rb");
9628         if (f == NULL) {
9629             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9630             DisplayError(buf, errno);
9631             return FALSE;
9632         } else {
9633             return LoadPosition(f, n, title);
9634         }
9635     }
9636 }
9637
9638 /* Load the nth position from the given open file, and close it */
9639 int
9640 LoadPosition(f, positionNumber, title)
9641      FILE *f;
9642      int positionNumber;
9643      char *title;
9644 {
9645     char *p, line[MSG_SIZ];
9646     Board initial_position;
9647     int i, j, fenMode, pn;
9648
9649     if (gameMode == Training )
9650         SetTrainingModeOff();
9651
9652     if (gameMode != BeginningOfGame) {
9653         Reset(FALSE, TRUE);
9654     }
9655     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9656         fclose(lastLoadPositionFP);
9657     }
9658     if (positionNumber == 0) positionNumber = 1;
9659     lastLoadPositionFP = f;
9660     lastLoadPositionNumber = positionNumber;
9661     strcpy(lastLoadPositionTitle, title);
9662     if (first.pr == NoProc) {
9663       StartChessProgram(&first);
9664       InitChessProgram(&first, FALSE);
9665     }
9666     pn = positionNumber;
9667     if (positionNumber < 0) {
9668         /* Negative position number means to seek to that byte offset */
9669         if (fseek(f, -positionNumber, 0) == -1) {
9670             DisplayError(_("Can't seek on position file"), 0);
9671             return FALSE;
9672         };
9673         pn = 1;
9674     } else {
9675         if (fseek(f, 0, 0) == -1) {
9676             if (f == lastLoadPositionFP ?
9677                 positionNumber == lastLoadPositionNumber + 1 :
9678                 positionNumber == 1) {
9679                 pn = 1;
9680             } else {
9681                 DisplayError(_("Can't seek on position file"), 0);
9682                 return FALSE;
9683             }
9684         }
9685     }
9686     /* See if this file is FEN or old-style xboard */
9687     if (fgets(line, MSG_SIZ, f) == NULL) {
9688         DisplayError(_("Position not found in file"), 0);
9689         return FALSE;
9690     }
9691     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9692     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9693
9694     if (pn >= 2) {
9695         if (fenMode || line[0] == '#') pn--;
9696         while (pn > 0) {
9697             /* skip positions before number pn */
9698             if (fgets(line, MSG_SIZ, f) == NULL) {
9699                 Reset(TRUE, TRUE);
9700                 DisplayError(_("Position not found in file"), 0);
9701                 return FALSE;
9702             }
9703             if (fenMode || line[0] == '#') pn--;
9704         }
9705     }
9706
9707     if (fenMode) {
9708         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9709             DisplayError(_("Bad FEN position in file"), 0);
9710             return FALSE;
9711         }
9712     } else {
9713         (void) fgets(line, MSG_SIZ, f);
9714         (void) fgets(line, MSG_SIZ, f);
9715
9716         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9717             (void) fgets(line, MSG_SIZ, f);
9718             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9719                 if (*p == ' ')
9720                   continue;
9721                 initial_position[i][j++] = CharToPiece(*p);
9722             }
9723         }
9724
9725         blackPlaysFirst = FALSE;
9726         if (!feof(f)) {
9727             (void) fgets(line, MSG_SIZ, f);
9728             if (strncmp(line, "black", strlen("black"))==0)
9729               blackPlaysFirst = TRUE;
9730         }
9731     }
9732     startedFromSetupPosition = TRUE;
9733
9734     SendToProgram("force\n", &first);
9735     CopyBoard(boards[0], initial_position);
9736     if (blackPlaysFirst) {
9737         currentMove = forwardMostMove = backwardMostMove = 1;
9738         strcpy(moveList[0], "");
9739         strcpy(parseList[0], "");
9740         CopyBoard(boards[1], initial_position);
9741         DisplayMessage("", _("Black to play"));
9742     } else {
9743         currentMove = forwardMostMove = backwardMostMove = 0;
9744         DisplayMessage("", _("White to play"));
9745     }
9746           /* [HGM] copy FEN attributes as well */
9747           {   int i;
9748               initialRulePlies = FENrulePlies;
9749               epStatus[forwardMostMove] = FENepStatus;
9750               for( i=0; i< nrCastlingRights; i++ )
9751                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9752           }
9753     SendBoard(&first, forwardMostMove);
9754     if (appData.debugMode) {
9755 int i, j;
9756   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9757   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9758         fprintf(debugFP, "Load Position\n");
9759     }
9760
9761     if (positionNumber > 1) {
9762         sprintf(line, "%s %d", title, positionNumber);
9763         DisplayTitle(line);
9764     } else {
9765         DisplayTitle(title);
9766     }
9767     gameMode = EditGame;
9768     ModeHighlight();
9769     ResetClocks();
9770     timeRemaining[0][1] = whiteTimeRemaining;
9771     timeRemaining[1][1] = blackTimeRemaining;
9772     DrawPosition(FALSE, boards[currentMove]);
9773
9774     return TRUE;
9775 }
9776
9777
9778 void
9779 CopyPlayerNameIntoFileName(dest, src)
9780      char **dest, *src;
9781 {
9782     while (*src != NULLCHAR && *src != ',') {
9783         if (*src == ' ') {
9784             *(*dest)++ = '_';
9785             src++;
9786         } else {
9787             *(*dest)++ = *src++;
9788         }
9789     }
9790 }
9791
9792 char *DefaultFileName(ext)
9793      char *ext;
9794 {
9795     static char def[MSG_SIZ];
9796     char *p;
9797
9798     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9799         p = def;
9800         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9801         *p++ = '-';
9802         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9803         *p++ = '.';
9804         strcpy(p, ext);
9805     } else {
9806         def[0] = NULLCHAR;
9807     }
9808     return def;
9809 }
9810
9811 /* Save the current game to the given file */
9812 int
9813 SaveGameToFile(filename, append)
9814      char *filename;
9815      int append;
9816 {
9817     FILE *f;
9818     char buf[MSG_SIZ];
9819
9820     if (strcmp(filename, "-") == 0) {
9821         return SaveGame(stdout, 0, NULL);
9822     } else {
9823         f = fopen(filename, append ? "a" : "w");
9824         if (f == NULL) {
9825             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9826             DisplayError(buf, errno);
9827             return FALSE;
9828         } else {
9829             return SaveGame(f, 0, NULL);
9830         }
9831     }
9832 }
9833
9834 char *
9835 SavePart(str)
9836      char *str;
9837 {
9838     static char buf[MSG_SIZ];
9839     char *p;
9840
9841     p = strchr(str, ' ');
9842     if (p == NULL) return str;
9843     strncpy(buf, str, p - str);
9844     buf[p - str] = NULLCHAR;
9845     return buf;
9846 }
9847
9848 #define PGN_MAX_LINE 75
9849
9850 #define PGN_SIDE_WHITE  0
9851 #define PGN_SIDE_BLACK  1
9852
9853 /* [AS] */
9854 static int FindFirstMoveOutOfBook( int side )
9855 {
9856     int result = -1;
9857
9858     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9859         int index = backwardMostMove;
9860         int has_book_hit = 0;
9861
9862         if( (index % 2) != side ) {
9863             index++;
9864         }
9865
9866         while( index < forwardMostMove ) {
9867             /* Check to see if engine is in book */
9868             int depth = pvInfoList[index].depth;
9869             int score = pvInfoList[index].score;
9870             int in_book = 0;
9871
9872             if( depth <= 2 ) {
9873                 in_book = 1;
9874             }
9875             else if( score == 0 && depth == 63 ) {
9876                 in_book = 1; /* Zappa */
9877             }
9878             else if( score == 2 && depth == 99 ) {
9879                 in_book = 1; /* Abrok */
9880             }
9881
9882             has_book_hit += in_book;
9883
9884             if( ! in_book ) {
9885                 result = index;
9886
9887                 break;
9888             }
9889
9890             index += 2;
9891         }
9892     }
9893
9894     return result;
9895 }
9896
9897 /* [AS] */
9898 void GetOutOfBookInfo( char * buf )
9899 {
9900     int oob[2];
9901     int i;
9902     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9903
9904     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9905     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9906
9907     *buf = '\0';
9908
9909     if( oob[0] >= 0 || oob[1] >= 0 ) {
9910         for( i=0; i<2; i++ ) {
9911             int idx = oob[i];
9912
9913             if( idx >= 0 ) {
9914                 if( i > 0 && oob[0] >= 0 ) {
9915                     strcat( buf, "   " );
9916                 }
9917
9918                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9919                 sprintf( buf+strlen(buf), "%s%.2f",
9920                     pvInfoList[idx].score >= 0 ? "+" : "",
9921                     pvInfoList[idx].score / 100.0 );
9922             }
9923         }
9924     }
9925 }
9926
9927 /* Save game in PGN style and close the file */
9928 int
9929 SaveGamePGN(f)
9930      FILE *f;
9931 {
9932     int i, offset, linelen, newblock;
9933     time_t tm;
9934 //    char *movetext;
9935     char numtext[32];
9936     int movelen, numlen, blank;
9937     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9938
9939     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9940
9941     tm = time((time_t *) NULL);
9942
9943     PrintPGNTags(f, &gameInfo);
9944
9945     if (backwardMostMove > 0 || startedFromSetupPosition) {
9946         char *fen = PositionToFEN(backwardMostMove, NULL);
9947         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9948         fprintf(f, "\n{--------------\n");
9949         PrintPosition(f, backwardMostMove);
9950         fprintf(f, "--------------}\n");
9951         free(fen);
9952     }
9953     else {
9954         /* [AS] Out of book annotation */
9955         if( appData.saveOutOfBookInfo ) {
9956             char buf[64];
9957
9958             GetOutOfBookInfo( buf );
9959
9960             if( buf[0] != '\0' ) {
9961                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9962             }
9963         }
9964
9965         fprintf(f, "\n");
9966     }
9967
9968     i = backwardMostMove;
9969     linelen = 0;
9970     newblock = TRUE;
9971
9972     while (i < forwardMostMove) {
9973         /* Print comments preceding this move */
9974         if (commentList[i] != NULL) {
9975             if (linelen > 0) fprintf(f, "\n");
9976             fprintf(f, "{\n%s}\n", commentList[i]);
9977             linelen = 0;
9978             newblock = TRUE;
9979         }
9980
9981         /* Format move number */
9982         if ((i % 2) == 0) {
9983             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9984         } else {
9985             if (newblock) {
9986                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9987             } else {
9988                 numtext[0] = NULLCHAR;
9989             }
9990         }
9991         numlen = strlen(numtext);
9992         newblock = FALSE;
9993
9994         /* Print move number */
9995         blank = linelen > 0 && numlen > 0;
9996         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9997             fprintf(f, "\n");
9998             linelen = 0;
9999             blank = 0;
10000         }
10001         if (blank) {
10002             fprintf(f, " ");
10003             linelen++;
10004         }
10005         fprintf(f, "%s", numtext);
10006         linelen += numlen;
10007
10008         /* Get move */
10009         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10010         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10011
10012         /* Print move */
10013         blank = linelen > 0 && movelen > 0;
10014         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10015             fprintf(f, "\n");
10016             linelen = 0;
10017             blank = 0;
10018         }
10019         if (blank) {
10020             fprintf(f, " ");
10021             linelen++;
10022         }
10023         fprintf(f, "%s", move_buffer);
10024         linelen += movelen;
10025
10026         /* [AS] Add PV info if present */
10027         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10028             /* [HGM] add time */
10029             char buf[MSG_SIZ]; int seconds = 0;
10030
10031             if(i >= backwardMostMove) {
10032                 if(WhiteOnMove(i))
10033                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
10034                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
10035                 else
10036                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
10037                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
10038             }
10039             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
10040
10041             if( seconds <= 0) buf[0] = 0; else
10042             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10043                 seconds = (seconds + 4)/10; // round to full seconds
10044                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10045                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10046             }
10047
10048             sprintf( move_buffer, "{%s%.2f/%d%s}",
10049                 pvInfoList[i].score >= 0 ? "+" : "",
10050                 pvInfoList[i].score / 100.0,
10051                 pvInfoList[i].depth,
10052                 buf );
10053
10054             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10055
10056             /* Print score/depth */
10057             blank = linelen > 0 && movelen > 0;
10058             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10059                 fprintf(f, "\n");
10060                 linelen = 0;
10061                 blank = 0;
10062             }
10063             if (blank) {
10064                 fprintf(f, " ");
10065                 linelen++;
10066             }
10067             fprintf(f, "%s", move_buffer);
10068             linelen += movelen;
10069         }
10070
10071         i++;
10072     }
10073
10074     /* Start a new line */
10075     if (linelen > 0) fprintf(f, "\n");
10076
10077     /* Print comments after last move */
10078     if (commentList[i] != NULL) {
10079         fprintf(f, "{\n%s}\n", commentList[i]);
10080     }
10081
10082     /* Print result */
10083     if (gameInfo.resultDetails != NULL &&
10084         gameInfo.resultDetails[0] != NULLCHAR) {
10085         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10086                 PGNResult(gameInfo.result));
10087     } else {
10088         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10089     }
10090
10091     fclose(f);
10092     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10093     return TRUE;
10094 }
10095
10096 /* Save game in old style and close the file */
10097 int
10098 SaveGameOldStyle(f)
10099      FILE *f;
10100 {
10101     int i, offset;
10102     time_t tm;
10103
10104     tm = time((time_t *) NULL);
10105
10106     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10107     PrintOpponents(f);
10108
10109     if (backwardMostMove > 0 || startedFromSetupPosition) {
10110         fprintf(f, "\n[--------------\n");
10111         PrintPosition(f, backwardMostMove);
10112         fprintf(f, "--------------]\n");
10113     } else {
10114         fprintf(f, "\n");
10115     }
10116
10117     i = backwardMostMove;
10118     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10119
10120     while (i < forwardMostMove) {
10121         if (commentList[i] != NULL) {
10122             fprintf(f, "[%s]\n", commentList[i]);
10123         }
10124
10125         if ((i % 2) == 1) {
10126             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10127             i++;
10128         } else {
10129             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10130             i++;
10131             if (commentList[i] != NULL) {
10132                 fprintf(f, "\n");
10133                 continue;
10134             }
10135             if (i >= forwardMostMove) {
10136                 fprintf(f, "\n");
10137                 break;
10138             }
10139             fprintf(f, "%s\n", parseList[i]);
10140             i++;
10141         }
10142     }
10143
10144     if (commentList[i] != NULL) {
10145         fprintf(f, "[%s]\n", commentList[i]);
10146     }
10147
10148     /* This isn't really the old style, but it's close enough */
10149     if (gameInfo.resultDetails != NULL &&
10150         gameInfo.resultDetails[0] != NULLCHAR) {
10151         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10152                 gameInfo.resultDetails);
10153     } else {
10154         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10155     }
10156
10157     fclose(f);
10158     return TRUE;
10159 }
10160
10161 /* Save the current game to open file f and close the file */
10162 int
10163 SaveGame(f, dummy, dummy2)
10164      FILE *f;
10165      int dummy;
10166      char *dummy2;
10167 {
10168     if (gameMode == EditPosition) EditPositionDone();
10169     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10170     if (appData.oldSaveStyle)
10171       return SaveGameOldStyle(f);
10172     else
10173       return SaveGamePGN(f);
10174 }
10175
10176 /* Save the current position to the given file */
10177 int
10178 SavePositionToFile(filename)
10179      char *filename;
10180 {
10181     FILE *f;
10182     char buf[MSG_SIZ];
10183
10184     if (strcmp(filename, "-") == 0) {
10185         return SavePosition(stdout, 0, NULL);
10186     } else {
10187         f = fopen(filename, "a");
10188         if (f == NULL) {
10189             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10190             DisplayError(buf, errno);
10191             return FALSE;
10192         } else {
10193             SavePosition(f, 0, NULL);
10194             return TRUE;
10195         }
10196     }
10197 }
10198
10199 /* Save the current position to the given open file and close the file */
10200 int
10201 SavePosition(f, dummy, dummy2)
10202      FILE *f;
10203      int dummy;
10204      char *dummy2;
10205 {
10206     time_t tm;
10207     char *fen;
10208
10209     if (appData.oldSaveStyle) {
10210         tm = time((time_t *) NULL);
10211
10212         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10213         PrintOpponents(f);
10214         fprintf(f, "[--------------\n");
10215         PrintPosition(f, currentMove);
10216         fprintf(f, "--------------]\n");
10217     } else {
10218         fen = PositionToFEN(currentMove, NULL);
10219         fprintf(f, "%s\n", fen);
10220         free(fen);
10221     }
10222     fclose(f);
10223     return TRUE;
10224 }
10225
10226 void
10227 ReloadCmailMsgEvent(unregister)
10228      int unregister;
10229 {
10230 #if !WIN32
10231     static char *inFilename = NULL;
10232     static char *outFilename;
10233     int i;
10234     struct stat inbuf, outbuf;
10235     int status;
10236
10237     /* Any registered moves are unregistered if unregister is set, */
10238     /* i.e. invoked by the signal handler */
10239     if (unregister) {
10240         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10241             cmailMoveRegistered[i] = FALSE;
10242             if (cmailCommentList[i] != NULL) {
10243                 free(cmailCommentList[i]);
10244                 cmailCommentList[i] = NULL;
10245             }
10246         }
10247         nCmailMovesRegistered = 0;
10248     }
10249
10250     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10251         cmailResult[i] = CMAIL_NOT_RESULT;
10252     }
10253     nCmailResults = 0;
10254
10255     if (inFilename == NULL) {
10256         /* Because the filenames are static they only get malloced once  */
10257         /* and they never get freed                                      */
10258         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10259         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10260
10261         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10262         sprintf(outFilename, "%s.out", appData.cmailGameName);
10263     }
10264
10265     status = stat(outFilename, &outbuf);
10266     if (status < 0) {
10267         cmailMailedMove = FALSE;
10268     } else {
10269         status = stat(inFilename, &inbuf);
10270         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10271     }
10272
10273     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10274        counts the games, notes how each one terminated, etc.
10275
10276        It would be nice to remove this kludge and instead gather all
10277        the information while building the game list.  (And to keep it
10278        in the game list nodes instead of having a bunch of fixed-size
10279        parallel arrays.)  Note this will require getting each game's
10280        termination from the PGN tags, as the game list builder does
10281        not process the game moves.  --mann
10282        */
10283     cmailMsgLoaded = TRUE;
10284     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10285
10286     /* Load first game in the file or popup game menu */
10287     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10288
10289 #endif /* !WIN32 */
10290     return;
10291 }
10292
10293 int
10294 RegisterMove()
10295 {
10296     FILE *f;
10297     char string[MSG_SIZ];
10298
10299     if (   cmailMailedMove
10300         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10301         return TRUE;            /* Allow free viewing  */
10302     }
10303
10304     /* Unregister move to ensure that we don't leave RegisterMove        */
10305     /* with the move registered when the conditions for registering no   */
10306     /* longer hold                                                       */
10307     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10308         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10309         nCmailMovesRegistered --;
10310
10311         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10312           {
10313               free(cmailCommentList[lastLoadGameNumber - 1]);
10314               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10315           }
10316     }
10317
10318     if (cmailOldMove == -1) {
10319         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10320         return FALSE;
10321     }
10322
10323     if (currentMove > cmailOldMove + 1) {
10324         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10325         return FALSE;
10326     }
10327
10328     if (currentMove < cmailOldMove) {
10329         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10330         return FALSE;
10331     }
10332
10333     if (forwardMostMove > currentMove) {
10334         /* Silently truncate extra moves */
10335         TruncateGame();
10336     }
10337
10338     if (   (currentMove == cmailOldMove + 1)
10339         || (   (currentMove == cmailOldMove)
10340             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10341                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10342         if (gameInfo.result != GameUnfinished) {
10343             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10344         }
10345
10346         if (commentList[currentMove] != NULL) {
10347             cmailCommentList[lastLoadGameNumber - 1]
10348               = StrSave(commentList[currentMove]);
10349         }
10350         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10351
10352         if (appData.debugMode)
10353           fprintf(debugFP, "Saving %s for game %d\n",
10354                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10355
10356         sprintf(string,
10357                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10358
10359         f = fopen(string, "w");
10360         if (appData.oldSaveStyle) {
10361             SaveGameOldStyle(f); /* also closes the file */
10362
10363             sprintf(string, "%s.pos.out", appData.cmailGameName);
10364             f = fopen(string, "w");
10365             SavePosition(f, 0, NULL); /* also closes the file */
10366         } else {
10367             fprintf(f, "{--------------\n");
10368             PrintPosition(f, currentMove);
10369             fprintf(f, "--------------}\n\n");
10370
10371             SaveGame(f, 0, NULL); /* also closes the file*/
10372         }
10373
10374         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10375         nCmailMovesRegistered ++;
10376     } else if (nCmailGames == 1) {
10377         DisplayError(_("You have not made a move yet"), 0);
10378         return FALSE;
10379     }
10380
10381     return TRUE;
10382 }
10383
10384 void
10385 MailMoveEvent()
10386 {
10387 #if !WIN32
10388     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10389     FILE *commandOutput;
10390     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10391     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10392     int nBuffers;
10393     int i;
10394     int archived;
10395     char *arcDir;
10396
10397     if (! cmailMsgLoaded) {
10398         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10399         return;
10400     }
10401
10402     if (nCmailGames == nCmailResults) {
10403         DisplayError(_("No unfinished games"), 0);
10404         return;
10405     }
10406
10407 #if CMAIL_PROHIBIT_REMAIL
10408     if (cmailMailedMove) {
10409         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);
10410         DisplayError(msg, 0);
10411         return;
10412     }
10413 #endif
10414
10415     if (! (cmailMailedMove || RegisterMove())) return;
10416
10417     if (   cmailMailedMove
10418         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10419         sprintf(string, partCommandString,
10420                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10421         commandOutput = popen(string, "r");
10422
10423         if (commandOutput == NULL) {
10424             DisplayError(_("Failed to invoke cmail"), 0);
10425         } else {
10426             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10427                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10428             }
10429             if (nBuffers > 1) {
10430                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10431                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10432                 nBytes = MSG_SIZ - 1;
10433             } else {
10434                 (void) memcpy(msg, buffer, nBytes);
10435             }
10436             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10437
10438             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10439                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10440
10441                 archived = TRUE;
10442                 for (i = 0; i < nCmailGames; i ++) {
10443                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10444                         archived = FALSE;
10445                     }
10446                 }
10447                 if (   archived
10448                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10449                         != NULL)) {
10450                     sprintf(buffer, "%s/%s.%s.archive",
10451                             arcDir,
10452                             appData.cmailGameName,
10453                             gameInfo.date);
10454                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10455                     cmailMsgLoaded = FALSE;
10456                 }
10457             }
10458
10459             DisplayInformation(msg);
10460             pclose(commandOutput);
10461         }
10462     } else {
10463         if ((*cmailMsg) != '\0') {
10464             DisplayInformation(cmailMsg);
10465         }
10466     }
10467
10468     return;
10469 #endif /* !WIN32 */
10470 }
10471
10472 char *
10473 CmailMsg()
10474 {
10475 #if WIN32
10476     return NULL;
10477 #else
10478     int  prependComma = 0;
10479     char number[5];
10480     char string[MSG_SIZ];       /* Space for game-list */
10481     int  i;
10482
10483     if (!cmailMsgLoaded) return "";
10484
10485     if (cmailMailedMove) {
10486         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10487     } else {
10488         /* Create a list of games left */
10489         sprintf(string, "[");
10490         for (i = 0; i < nCmailGames; i ++) {
10491             if (! (   cmailMoveRegistered[i]
10492                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10493                 if (prependComma) {
10494                     sprintf(number, ",%d", i + 1);
10495                 } else {
10496                     sprintf(number, "%d", i + 1);
10497                     prependComma = 1;
10498                 }
10499
10500                 strcat(string, number);
10501             }
10502         }
10503         strcat(string, "]");
10504
10505         if (nCmailMovesRegistered + nCmailResults == 0) {
10506             switch (nCmailGames) {
10507               case 1:
10508                 sprintf(cmailMsg,
10509                         _("Still need to make move for game\n"));
10510                 break;
10511
10512               case 2:
10513                 sprintf(cmailMsg,
10514                         _("Still need to make moves for both games\n"));
10515                 break;
10516
10517               default:
10518                 sprintf(cmailMsg,
10519                         _("Still need to make moves for all %d games\n"),
10520                         nCmailGames);
10521                 break;
10522             }
10523         } else {
10524             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10525               case 1:
10526                 sprintf(cmailMsg,
10527                         _("Still need to make a move for game %s\n"),
10528                         string);
10529                 break;
10530
10531               case 0:
10532                 if (nCmailResults == nCmailGames) {
10533                     sprintf(cmailMsg, _("No unfinished games\n"));
10534                 } else {
10535                     sprintf(cmailMsg, _("Ready to send mail\n"));
10536                 }
10537                 break;
10538
10539               default:
10540                 sprintf(cmailMsg,
10541                         _("Still need to make moves for games %s\n"),
10542                         string);
10543             }
10544         }
10545     }
10546     return cmailMsg;
10547 #endif /* WIN32 */
10548 }
10549
10550 void
10551 ResetGameEvent()
10552 {
10553     if (gameMode == Training)
10554       SetTrainingModeOff();
10555
10556     Reset(TRUE, TRUE);
10557     cmailMsgLoaded = FALSE;
10558     if (appData.icsActive) {
10559       SendToICS(ics_prefix);
10560       SendToICS("refresh\n");
10561     }
10562 }
10563
10564 void
10565 ExitEvent(status)
10566      int status;
10567 {
10568     exiting++;
10569     if (exiting > 2) {
10570       /* Give up on clean exit */
10571       exit(status);
10572     }
10573     if (exiting > 1) {
10574       /* Keep trying for clean exit */
10575       return;
10576     }
10577
10578     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10579
10580     if (telnetISR != NULL) {
10581       RemoveInputSource(telnetISR);
10582     }
10583     if (icsPR != NoProc) {
10584       DestroyChildProcess(icsPR, TRUE);
10585     }
10586
10587     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10588     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10589
10590     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10591     /* make sure this other one finishes before killing it!                  */
10592     if(endingGame) { int count = 0;
10593         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10594         while(endingGame && count++ < 10) DoSleep(1);
10595         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10596     }
10597
10598     /* Kill off chess programs */
10599     if (first.pr != NoProc) {
10600         ExitAnalyzeMode();
10601
10602         DoSleep( appData.delayBeforeQuit );
10603         SendToProgram("quit\n", &first);
10604         DoSleep( appData.delayAfterQuit );
10605         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10606     }
10607     if (second.pr != NoProc) {
10608         DoSleep( appData.delayBeforeQuit );
10609         SendToProgram("quit\n", &second);
10610         DoSleep( appData.delayAfterQuit );
10611         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10612     }
10613     if (first.isr != NULL) {
10614         RemoveInputSource(first.isr);
10615     }
10616     if (second.isr != NULL) {
10617         RemoveInputSource(second.isr);
10618     }
10619
10620     ShutDownFrontEnd();
10621     exit(status);
10622 }
10623
10624 void
10625 PauseEvent()
10626 {
10627     if (appData.debugMode)
10628         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10629     if (pausing) {
10630         pausing = FALSE;
10631         ModeHighlight();
10632         if (gameMode == MachinePlaysWhite ||
10633             gameMode == MachinePlaysBlack) {
10634             StartClocks();
10635         } else {
10636             DisplayBothClocks();
10637         }
10638         if (gameMode == PlayFromGameFile) {
10639             if (appData.timeDelay >= 0)
10640                 AutoPlayGameLoop();
10641         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10642             Reset(FALSE, TRUE);
10643             SendToICS(ics_prefix);
10644             SendToICS("refresh\n");
10645         } else if (currentMove < forwardMostMove) {
10646             ForwardInner(forwardMostMove);
10647         }
10648         pauseExamInvalid = FALSE;
10649     } else {
10650         switch (gameMode) {
10651           default:
10652             return;
10653           case IcsExamining:
10654             pauseExamForwardMostMove = forwardMostMove;
10655             pauseExamInvalid = FALSE;
10656             /* fall through */
10657           case IcsObserving:
10658           case IcsPlayingWhite:
10659           case IcsPlayingBlack:
10660             pausing = TRUE;
10661             ModeHighlight();
10662             return;
10663           case PlayFromGameFile:
10664             (void) StopLoadGameTimer();
10665             pausing = TRUE;
10666             ModeHighlight();
10667             break;
10668           case BeginningOfGame:
10669             if (appData.icsActive) return;
10670             /* else fall through */
10671           case MachinePlaysWhite:
10672           case MachinePlaysBlack:
10673           case TwoMachinesPlay:
10674             if (forwardMostMove == 0)
10675               return;           /* don't pause if no one has moved */
10676             if ((gameMode == MachinePlaysWhite &&
10677                  !WhiteOnMove(forwardMostMove)) ||
10678                 (gameMode == MachinePlaysBlack &&
10679                  WhiteOnMove(forwardMostMove))) {
10680                 StopClocks();
10681             }
10682             pausing = TRUE;
10683             ModeHighlight();
10684             break;
10685         }
10686     }
10687 }
10688
10689 void
10690 EditCommentEvent()
10691 {
10692     char title[MSG_SIZ];
10693
10694     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10695         strcpy(title, _("Edit comment"));
10696     } else {
10697         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10698                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10699                 parseList[currentMove - 1]);
10700     }
10701
10702     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10703 }
10704
10705
10706 void
10707 EditTagsEvent()
10708 {
10709     char *tags = PGNTags(&gameInfo);
10710     EditTagsPopUp(tags);
10711     free(tags);
10712 }
10713
10714 void
10715 AnalyzeModeEvent()
10716 {
10717     if (appData.noChessProgram || gameMode == AnalyzeMode)
10718       return;
10719
10720     if (gameMode != AnalyzeFile) {
10721         if (!appData.icsEngineAnalyze) {
10722                EditGameEvent();
10723                if (gameMode != EditGame) return;
10724         }
10725         ResurrectChessProgram();
10726         SendToProgram("analyze\n", &first);
10727         first.analyzing = TRUE;
10728         /*first.maybeThinking = TRUE;*/
10729         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10730         EngineOutputPopUp();
10731     }
10732     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10733     pausing = FALSE;
10734     ModeHighlight();
10735     SetGameInfo();
10736
10737     StartAnalysisClock();
10738     GetTimeMark(&lastNodeCountTime);
10739     lastNodeCount = 0;
10740 }
10741
10742 void
10743 AnalyzeFileEvent()
10744 {
10745     if (appData.noChessProgram || gameMode == AnalyzeFile)
10746       return;
10747
10748     if (gameMode != AnalyzeMode) {
10749         EditGameEvent();
10750         if (gameMode != EditGame) return;
10751         ResurrectChessProgram();
10752         SendToProgram("analyze\n", &first);
10753         first.analyzing = TRUE;
10754         /*first.maybeThinking = TRUE;*/
10755         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10756         EngineOutputPopUp();
10757     }
10758     gameMode = AnalyzeFile;
10759     pausing = FALSE;
10760     ModeHighlight();
10761     SetGameInfo();
10762
10763     StartAnalysisClock();
10764     GetTimeMark(&lastNodeCountTime);
10765     lastNodeCount = 0;
10766 }
10767
10768 void
10769 MachineWhiteEvent()
10770 {
10771     char buf[MSG_SIZ];
10772     char *bookHit = NULL;
10773
10774     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10775       return;
10776
10777
10778     if (gameMode == PlayFromGameFile ||
10779         gameMode == TwoMachinesPlay  ||
10780         gameMode == Training         ||
10781         gameMode == AnalyzeMode      ||
10782         gameMode == EndOfGame)
10783         EditGameEvent();
10784
10785     if (gameMode == EditPosition)
10786         EditPositionDone();
10787
10788     if (!WhiteOnMove(currentMove)) {
10789         DisplayError(_("It is not White's turn"), 0);
10790         return;
10791     }
10792
10793     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10794       ExitAnalyzeMode();
10795
10796     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10797         gameMode == AnalyzeFile)
10798         TruncateGame();
10799
10800     ResurrectChessProgram();    /* in case it isn't running */
10801     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10802         gameMode = MachinePlaysWhite;
10803         ResetClocks();
10804     } else
10805     gameMode = MachinePlaysWhite;
10806     pausing = FALSE;
10807     ModeHighlight();
10808     SetGameInfo();
10809     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10810     DisplayTitle(buf);
10811     if (first.sendName) {
10812       sprintf(buf, "name %s\n", gameInfo.black);
10813       SendToProgram(buf, &first);
10814     }
10815     if (first.sendTime) {
10816       if (first.useColors) {
10817         SendToProgram("black\n", &first); /*gnu kludge*/
10818       }
10819       SendTimeRemaining(&first, TRUE);
10820     }
10821     if (first.useColors) {
10822       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10823     }
10824     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10825     SetMachineThinkingEnables();
10826     first.maybeThinking = TRUE;
10827     StartClocks();
10828     firstMove = FALSE;
10829
10830     if (appData.autoFlipView && !flipView) {
10831       flipView = !flipView;
10832       DrawPosition(FALSE, NULL);
10833       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10834     }
10835
10836     if(bookHit) { // [HGM] book: simulate book reply
10837         static char bookMove[MSG_SIZ]; // a bit generous?
10838
10839         programStats.nodes = programStats.depth = programStats.time =
10840         programStats.score = programStats.got_only_move = 0;
10841         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10842
10843         strcpy(bookMove, "move ");
10844         strcat(bookMove, bookHit);
10845         HandleMachineMove(bookMove, &first);
10846     }
10847 }
10848
10849 void
10850 MachineBlackEvent()
10851 {
10852     char buf[MSG_SIZ];
10853    char *bookHit = NULL;
10854
10855     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10856         return;
10857
10858
10859     if (gameMode == PlayFromGameFile ||
10860         gameMode == TwoMachinesPlay  ||
10861         gameMode == Training         ||
10862         gameMode == AnalyzeMode      ||
10863         gameMode == EndOfGame)
10864         EditGameEvent();
10865
10866     if (gameMode == EditPosition)
10867         EditPositionDone();
10868
10869     if (WhiteOnMove(currentMove)) {
10870         DisplayError(_("It is not Black's turn"), 0);
10871         return;
10872     }
10873
10874     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10875       ExitAnalyzeMode();
10876
10877     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10878         gameMode == AnalyzeFile)
10879         TruncateGame();
10880
10881     ResurrectChessProgram();    /* in case it isn't running */
10882     gameMode = MachinePlaysBlack;
10883     pausing = FALSE;
10884     ModeHighlight();
10885     SetGameInfo();
10886     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10887     DisplayTitle(buf);
10888     if (first.sendName) {
10889       sprintf(buf, "name %s\n", gameInfo.white);
10890       SendToProgram(buf, &first);
10891     }
10892     if (first.sendTime) {
10893       if (first.useColors) {
10894         SendToProgram("white\n", &first); /*gnu kludge*/
10895       }
10896       SendTimeRemaining(&first, FALSE);
10897     }
10898     if (first.useColors) {
10899       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10900     }
10901     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10902     SetMachineThinkingEnables();
10903     first.maybeThinking = TRUE;
10904     StartClocks();
10905
10906     if (appData.autoFlipView && flipView) {
10907       flipView = !flipView;
10908       DrawPosition(FALSE, NULL);
10909       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10910     }
10911     if(bookHit) { // [HGM] book: simulate book reply
10912         static char bookMove[MSG_SIZ]; // a bit generous?
10913
10914         programStats.nodes = programStats.depth = programStats.time =
10915         programStats.score = programStats.got_only_move = 0;
10916         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10917
10918         strcpy(bookMove, "move ");
10919         strcat(bookMove, bookHit);
10920         HandleMachineMove(bookMove, &first);
10921     }
10922 }
10923
10924
10925 void
10926 DisplayTwoMachinesTitle()
10927 {
10928     char buf[MSG_SIZ];
10929     if (appData.matchGames > 0) {
10930         if (first.twoMachinesColor[0] == 'w') {
10931             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10932                     gameInfo.white, gameInfo.black,
10933                     first.matchWins, second.matchWins,
10934                     matchGame - 1 - (first.matchWins + second.matchWins));
10935         } else {
10936             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10937                     gameInfo.white, gameInfo.black,
10938                     second.matchWins, first.matchWins,
10939                     matchGame - 1 - (first.matchWins + second.matchWins));
10940         }
10941     } else {
10942         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10943     }
10944     DisplayTitle(buf);
10945 }
10946
10947 void
10948 TwoMachinesEvent P((void))
10949 {
10950     int i;
10951     char buf[MSG_SIZ];
10952     ChessProgramState *onmove;
10953     char *bookHit = NULL;
10954
10955     if (appData.noChessProgram) return;
10956
10957     switch (gameMode) {
10958       case TwoMachinesPlay:
10959         return;
10960       case MachinePlaysWhite:
10961       case MachinePlaysBlack:
10962         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10963             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10964             return;
10965         }
10966         /* fall through */
10967       case BeginningOfGame:
10968       case PlayFromGameFile:
10969       case EndOfGame:
10970         EditGameEvent();
10971         if (gameMode != EditGame) return;
10972         break;
10973       case EditPosition:
10974         EditPositionDone();
10975         break;
10976       case AnalyzeMode:
10977       case AnalyzeFile:
10978         ExitAnalyzeMode();
10979         break;
10980       case EditGame:
10981       default:
10982         break;
10983     }
10984
10985     forwardMostMove = currentMove;
10986     ResurrectChessProgram();    /* in case first program isn't running */
10987
10988     if (second.pr == NULL) {
10989         StartChessProgram(&second);
10990         if (second.protocolVersion == 1) {
10991           TwoMachinesEventIfReady();
10992         } else {
10993           /* kludge: allow timeout for initial "feature" command */
10994           FreezeUI();
10995           DisplayMessage("", _("Starting second chess program"));
10996           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10997         }
10998         return;
10999     }
11000     DisplayMessage("", "");
11001     InitChessProgram(&second, FALSE);
11002     SendToProgram("force\n", &second);
11003     if (startedFromSetupPosition) {
11004         SendBoard(&second, backwardMostMove);
11005     if (appData.debugMode) {
11006         fprintf(debugFP, "Two Machines\n");
11007     }
11008     }
11009     for (i = backwardMostMove; i < forwardMostMove; i++) {
11010         SendMoveToProgram(i, &second);
11011     }
11012
11013     gameMode = TwoMachinesPlay;
11014     pausing = FALSE;
11015     ModeHighlight();
11016     SetGameInfo();
11017     DisplayTwoMachinesTitle();
11018     firstMove = TRUE;
11019     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11020         onmove = &first;
11021     } else {
11022         onmove = &second;
11023     }
11024
11025     SendToProgram(first.computerString, &first);
11026     if (first.sendName) {
11027       sprintf(buf, "name %s\n", second.tidy);
11028       SendToProgram(buf, &first);
11029     }
11030     SendToProgram(second.computerString, &second);
11031     if (second.sendName) {
11032       sprintf(buf, "name %s\n", first.tidy);
11033       SendToProgram(buf, &second);
11034     }
11035
11036     ResetClocks();
11037     if (!first.sendTime || !second.sendTime) {
11038         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11039         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11040     }
11041     if (onmove->sendTime) {
11042       if (onmove->useColors) {
11043         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11044       }
11045       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11046     }
11047     if (onmove->useColors) {
11048       SendToProgram(onmove->twoMachinesColor, onmove);
11049     }
11050     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11051 //    SendToProgram("go\n", onmove);
11052     onmove->maybeThinking = TRUE;
11053     SetMachineThinkingEnables();
11054
11055     StartClocks();
11056
11057     if(bookHit) { // [HGM] book: simulate book reply
11058         static char bookMove[MSG_SIZ]; // a bit generous?
11059
11060         programStats.nodes = programStats.depth = programStats.time =
11061         programStats.score = programStats.got_only_move = 0;
11062         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11063
11064         strcpy(bookMove, "move ");
11065         strcat(bookMove, bookHit);
11066         savedMessage = bookMove; // args for deferred call
11067         savedState = onmove;
11068         ScheduleDelayedEvent(DeferredBookMove, 1);
11069     }
11070 }
11071
11072 void
11073 TrainingEvent()
11074 {
11075     if (gameMode == Training) {
11076       SetTrainingModeOff();
11077       gameMode = PlayFromGameFile;
11078       DisplayMessage("", _("Training mode off"));
11079     } else {
11080       gameMode = Training;
11081       animateTraining = appData.animate;
11082
11083       /* make sure we are not already at the end of the game */
11084       if (currentMove < forwardMostMove) {
11085         SetTrainingModeOn();
11086         DisplayMessage("", _("Training mode on"));
11087       } else {
11088         gameMode = PlayFromGameFile;
11089         DisplayError(_("Already at end of game"), 0);
11090       }
11091     }
11092     ModeHighlight();
11093 }
11094
11095 void
11096 IcsClientEvent()
11097 {
11098     if (!appData.icsActive) return;
11099     switch (gameMode) {
11100       case IcsPlayingWhite:
11101       case IcsPlayingBlack:
11102       case IcsObserving:
11103       case IcsIdle:
11104       case BeginningOfGame:
11105       case IcsExamining:
11106         return;
11107
11108       case EditGame:
11109         break;
11110
11111       case EditPosition:
11112         EditPositionDone();
11113         break;
11114
11115       case AnalyzeMode:
11116       case AnalyzeFile:
11117         ExitAnalyzeMode();
11118         break;
11119
11120       default:
11121         EditGameEvent();
11122         break;
11123     }
11124
11125     gameMode = IcsIdle;
11126     ModeHighlight();
11127     return;
11128 }
11129
11130
11131 void
11132 EditGameEvent()
11133 {
11134     int i;
11135
11136     switch (gameMode) {
11137       case Training:
11138         SetTrainingModeOff();
11139         break;
11140       case MachinePlaysWhite:
11141       case MachinePlaysBlack:
11142       case BeginningOfGame:
11143         SendToProgram("force\n", &first);
11144         SetUserThinkingEnables();
11145         break;
11146       case PlayFromGameFile:
11147         (void) StopLoadGameTimer();
11148         if (gameFileFP != NULL) {
11149             gameFileFP = NULL;
11150         }
11151         break;
11152       case EditPosition:
11153         EditPositionDone();
11154         break;
11155       case AnalyzeMode:
11156       case AnalyzeFile:
11157         ExitAnalyzeMode();
11158         SendToProgram("force\n", &first);
11159         break;
11160       case TwoMachinesPlay:
11161         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11162         ResurrectChessProgram();
11163         SetUserThinkingEnables();
11164         break;
11165       case EndOfGame:
11166         ResurrectChessProgram();
11167         break;
11168       case IcsPlayingBlack:
11169       case IcsPlayingWhite:
11170         DisplayError(_("Warning: You are still playing a game"), 0);
11171         break;
11172       case IcsObserving:
11173         DisplayError(_("Warning: You are still observing a game"), 0);
11174         break;
11175       case IcsExamining:
11176         DisplayError(_("Warning: You are still examining a game"), 0);
11177         break;
11178       case IcsIdle:
11179         break;
11180       case EditGame:
11181       default:
11182         return;
11183     }
11184
11185     pausing = FALSE;
11186     StopClocks();
11187     first.offeredDraw = second.offeredDraw = 0;
11188
11189     if (gameMode == PlayFromGameFile) {
11190         whiteTimeRemaining = timeRemaining[0][currentMove];
11191         blackTimeRemaining = timeRemaining[1][currentMove];
11192         DisplayTitle("");
11193     }
11194
11195     if (gameMode == MachinePlaysWhite ||
11196         gameMode == MachinePlaysBlack ||
11197         gameMode == TwoMachinesPlay ||
11198         gameMode == EndOfGame) {
11199         i = forwardMostMove;
11200         while (i > currentMove) {
11201             SendToProgram("undo\n", &first);
11202             i--;
11203         }
11204         whiteTimeRemaining = timeRemaining[0][currentMove];
11205         blackTimeRemaining = timeRemaining[1][currentMove];
11206         DisplayBothClocks();
11207         if (whiteFlag || blackFlag) {
11208             whiteFlag = blackFlag = 0;
11209         }
11210         DisplayTitle("");
11211     }
11212
11213     gameMode = EditGame;
11214     ModeHighlight();
11215     SetGameInfo();
11216 }
11217
11218
11219 void
11220 EditPositionEvent()
11221 {
11222     if (gameMode == EditPosition) {
11223         EditGameEvent();
11224         return;
11225     }
11226
11227     EditGameEvent();
11228     if (gameMode != EditGame) return;
11229
11230     gameMode = EditPosition;
11231     ModeHighlight();
11232     SetGameInfo();
11233     if (currentMove > 0)
11234       CopyBoard(boards[0], boards[currentMove]);
11235
11236     blackPlaysFirst = !WhiteOnMove(currentMove);
11237     ResetClocks();
11238     currentMove = forwardMostMove = backwardMostMove = 0;
11239     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11240     DisplayMove(-1);
11241 }
11242
11243 void
11244 ExitAnalyzeMode()
11245 {
11246     /* [DM] icsEngineAnalyze - possible call from other functions */
11247     if (appData.icsEngineAnalyze) {
11248         appData.icsEngineAnalyze = FALSE;
11249
11250         DisplayMessage("",_("Close ICS engine analyze..."));
11251     }
11252     if (first.analysisSupport && first.analyzing) {
11253       SendToProgram("exit\n", &first);
11254       first.analyzing = FALSE;
11255     }
11256     thinkOutput[0] = NULLCHAR;
11257 }
11258
11259 void
11260 EditPositionDone()
11261 {
11262     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11263
11264     startedFromSetupPosition = TRUE;
11265     InitChessProgram(&first, FALSE);
11266     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11267     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11268         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11269         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11270     } else castlingRights[0][2] = -1;
11271     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11272         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11273         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11274     } else castlingRights[0][5] = -1;
11275     SendToProgram("force\n", &first);
11276     if (blackPlaysFirst) {
11277         strcpy(moveList[0], "");
11278         strcpy(parseList[0], "");
11279         currentMove = forwardMostMove = backwardMostMove = 1;
11280         CopyBoard(boards[1], boards[0]);
11281         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11282         { int i;
11283           epStatus[1] = epStatus[0];
11284           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11285         }
11286     } else {
11287         currentMove = forwardMostMove = backwardMostMove = 0;
11288     }
11289     SendBoard(&first, forwardMostMove);
11290     if (appData.debugMode) {
11291         fprintf(debugFP, "EditPosDone\n");
11292     }
11293     DisplayTitle("");
11294     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11295     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11296     gameMode = EditGame;
11297     ModeHighlight();
11298     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11299     ClearHighlights(); /* [AS] */
11300 }
11301
11302 /* Pause for `ms' milliseconds */
11303 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11304 void
11305 TimeDelay(ms)
11306      long ms;
11307 {
11308     TimeMark m1, m2;
11309
11310     GetTimeMark(&m1);
11311     do {
11312         GetTimeMark(&m2);
11313     } while (SubtractTimeMarks(&m2, &m1) < ms);
11314 }
11315
11316 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11317 void
11318 SendMultiLineToICS(buf)
11319      char *buf;
11320 {
11321     char temp[MSG_SIZ+1], *p;
11322     int len;
11323
11324     len = strlen(buf);
11325     if (len > MSG_SIZ)
11326       len = MSG_SIZ;
11327
11328     strncpy(temp, buf, len);
11329     temp[len] = 0;
11330
11331     p = temp;
11332     while (*p) {
11333         if (*p == '\n' || *p == '\r')
11334           *p = ' ';
11335         ++p;
11336     }
11337
11338     strcat(temp, "\n");
11339     SendToICS(temp);
11340     SendToPlayer(temp, strlen(temp));
11341 }
11342
11343 void
11344 SetWhiteToPlayEvent()
11345 {
11346     if (gameMode == EditPosition) {
11347         blackPlaysFirst = FALSE;
11348         DisplayBothClocks();    /* works because currentMove is 0 */
11349     } else if (gameMode == IcsExamining) {
11350         SendToICS(ics_prefix);
11351         SendToICS("tomove white\n");
11352     }
11353 }
11354
11355 void
11356 SetBlackToPlayEvent()
11357 {
11358     if (gameMode == EditPosition) {
11359         blackPlaysFirst = TRUE;
11360         currentMove = 1;        /* kludge */
11361         DisplayBothClocks();
11362         currentMove = 0;
11363     } else if (gameMode == IcsExamining) {
11364         SendToICS(ics_prefix);
11365         SendToICS("tomove black\n");
11366     }
11367 }
11368
11369 void
11370 EditPositionMenuEvent(selection, x, y)
11371      ChessSquare selection;
11372      int x, y;
11373 {
11374     char buf[MSG_SIZ];
11375     ChessSquare piece = boards[0][y][x];
11376
11377     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11378
11379     switch (selection) {
11380       case ClearBoard:
11381         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11382             SendToICS(ics_prefix);
11383             SendToICS("bsetup clear\n");
11384         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11385             SendToICS(ics_prefix);
11386             SendToICS("clearboard\n");
11387         } else {
11388             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11389                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11390                 for (y = 0; y < BOARD_HEIGHT; y++) {
11391                     if (gameMode == IcsExamining) {
11392                         if (boards[currentMove][y][x] != EmptySquare) {
11393                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11394                                     AAA + x, ONE + y);
11395                             SendToICS(buf);
11396                         }
11397                     } else {
11398                         boards[0][y][x] = p;
11399                     }
11400                 }
11401             }
11402         }
11403         if (gameMode == EditPosition) {
11404             DrawPosition(FALSE, boards[0]);
11405         }
11406         break;
11407
11408       case WhitePlay:
11409         SetWhiteToPlayEvent();
11410         break;
11411
11412       case BlackPlay:
11413         SetBlackToPlayEvent();
11414         break;
11415
11416       case EmptySquare:
11417         if (gameMode == IcsExamining) {
11418             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11419             SendToICS(buf);
11420         } else {
11421             boards[0][y][x] = EmptySquare;
11422             DrawPosition(FALSE, boards[0]);
11423         }
11424         break;
11425
11426       case PromotePiece:
11427         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11428            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11429             selection = (ChessSquare) (PROMOTED piece);
11430         } else if(piece == EmptySquare) selection = WhiteSilver;
11431         else selection = (ChessSquare)((int)piece - 1);
11432         goto defaultlabel;
11433
11434       case DemotePiece:
11435         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11436            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11437             selection = (ChessSquare) (DEMOTED piece);
11438         } else if(piece == EmptySquare) selection = BlackSilver;
11439         else selection = (ChessSquare)((int)piece + 1);
11440         goto defaultlabel;
11441
11442       case WhiteQueen:
11443       case BlackQueen:
11444         if(gameInfo.variant == VariantShatranj ||
11445            gameInfo.variant == VariantXiangqi  ||
11446            gameInfo.variant == VariantCourier    )
11447             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11448         goto defaultlabel;
11449
11450       case WhiteKing:
11451       case BlackKing:
11452         if(gameInfo.variant == VariantXiangqi)
11453             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11454         if(gameInfo.variant == VariantKnightmate)
11455             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11456       default:
11457         defaultlabel:
11458         if (gameMode == IcsExamining) {
11459             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11460                     PieceToChar(selection), AAA + x, ONE + y);
11461             SendToICS(buf);
11462         } else {
11463             boards[0][y][x] = selection;
11464             DrawPosition(FALSE, boards[0]);
11465         }
11466         break;
11467     }
11468 }
11469
11470
11471 void
11472 DropMenuEvent(selection, x, y)
11473      ChessSquare selection;
11474      int x, y;
11475 {
11476     ChessMove moveType;
11477
11478     switch (gameMode) {
11479       case IcsPlayingWhite:
11480       case MachinePlaysBlack:
11481         if (!WhiteOnMove(currentMove)) {
11482             DisplayMoveError(_("It is Black's turn"));
11483             return;
11484         }
11485         moveType = WhiteDrop;
11486         break;
11487       case IcsPlayingBlack:
11488       case MachinePlaysWhite:
11489         if (WhiteOnMove(currentMove)) {
11490             DisplayMoveError(_("It is White's turn"));
11491             return;
11492         }
11493         moveType = BlackDrop;
11494         break;
11495       case EditGame:
11496         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11497         break;
11498       default:
11499         return;
11500     }
11501
11502     if (moveType == BlackDrop && selection < BlackPawn) {
11503       selection = (ChessSquare) ((int) selection
11504                                  + (int) BlackPawn - (int) WhitePawn);
11505     }
11506     if (boards[currentMove][y][x] != EmptySquare) {
11507         DisplayMoveError(_("That square is occupied"));
11508         return;
11509     }
11510
11511     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11512 }
11513
11514 void
11515 AcceptEvent()
11516 {
11517     /* Accept a pending offer of any kind from opponent */
11518
11519     if (appData.icsActive) {
11520         SendToICS(ics_prefix);
11521         SendToICS("accept\n");
11522     } else if (cmailMsgLoaded) {
11523         if (currentMove == cmailOldMove &&
11524             commentList[cmailOldMove] != NULL &&
11525             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11526                    "Black offers a draw" : "White offers a draw")) {
11527             TruncateGame();
11528             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11529             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11530         } else {
11531             DisplayError(_("There is no pending offer on this move"), 0);
11532             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11533         }
11534     } else {
11535         /* Not used for offers from chess program */
11536     }
11537 }
11538
11539 void
11540 DeclineEvent()
11541 {
11542     /* Decline a pending offer of any kind from opponent */
11543
11544     if (appData.icsActive) {
11545         SendToICS(ics_prefix);
11546         SendToICS("decline\n");
11547     } else if (cmailMsgLoaded) {
11548         if (currentMove == cmailOldMove &&
11549             commentList[cmailOldMove] != NULL &&
11550             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11551                    "Black offers a draw" : "White offers a draw")) {
11552 #ifdef NOTDEF
11553             AppendComment(cmailOldMove, "Draw declined");
11554             DisplayComment(cmailOldMove - 1, "Draw declined");
11555 #endif /*NOTDEF*/
11556         } else {
11557             DisplayError(_("There is no pending offer on this move"), 0);
11558         }
11559     } else {
11560         /* Not used for offers from chess program */
11561     }
11562 }
11563
11564 void
11565 RematchEvent()
11566 {
11567     /* Issue ICS rematch command */
11568     if (appData.icsActive) {
11569         SendToICS(ics_prefix);
11570         SendToICS("rematch\n");
11571     }
11572 }
11573
11574 void
11575 CallFlagEvent()
11576 {
11577     /* Call your opponent's flag (claim a win on time) */
11578     if (appData.icsActive) {
11579         SendToICS(ics_prefix);
11580         SendToICS("flag\n");
11581     } else {
11582         switch (gameMode) {
11583           default:
11584             return;
11585           case MachinePlaysWhite:
11586             if (whiteFlag) {
11587                 if (blackFlag)
11588                   GameEnds(GameIsDrawn, "Both players ran out of time",
11589                            GE_PLAYER);
11590                 else
11591                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11592             } else {
11593                 DisplayError(_("Your opponent is not out of time"), 0);
11594             }
11595             break;
11596           case MachinePlaysBlack:
11597             if (blackFlag) {
11598                 if (whiteFlag)
11599                   GameEnds(GameIsDrawn, "Both players ran out of time",
11600                            GE_PLAYER);
11601                 else
11602                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11603             } else {
11604                 DisplayError(_("Your opponent is not out of time"), 0);
11605             }
11606             break;
11607         }
11608     }
11609 }
11610
11611 void
11612 DrawEvent()
11613 {
11614     /* Offer draw or accept pending draw offer from opponent */
11615
11616     if (appData.icsActive) {
11617         /* Note: tournament rules require draw offers to be
11618            made after you make your move but before you punch
11619            your clock.  Currently ICS doesn't let you do that;
11620            instead, you immediately punch your clock after making
11621            a move, but you can offer a draw at any time. */
11622
11623         SendToICS(ics_prefix);
11624         SendToICS("draw\n");
11625     } else if (cmailMsgLoaded) {
11626         if (currentMove == cmailOldMove &&
11627             commentList[cmailOldMove] != NULL &&
11628             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11629                    "Black offers a draw" : "White offers a draw")) {
11630             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11631             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11632         } else if (currentMove == cmailOldMove + 1) {
11633             char *offer = WhiteOnMove(cmailOldMove) ?
11634               "White offers a draw" : "Black offers a draw";
11635             AppendComment(currentMove, offer);
11636             DisplayComment(currentMove - 1, offer);
11637             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11638         } else {
11639             DisplayError(_("You must make your move before offering a draw"), 0);
11640             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11641         }
11642     } else if (first.offeredDraw) {
11643         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11644     } else {
11645         if (first.sendDrawOffers) {
11646             SendToProgram("draw\n", &first);
11647             userOfferedDraw = TRUE;
11648         }
11649     }
11650 }
11651
11652 void
11653 AdjournEvent()
11654 {
11655     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11656
11657     if (appData.icsActive) {
11658         SendToICS(ics_prefix);
11659         SendToICS("adjourn\n");
11660     } else {
11661         /* Currently GNU Chess doesn't offer or accept Adjourns */
11662     }
11663 }
11664
11665
11666 void
11667 AbortEvent()
11668 {
11669     /* Offer Abort or accept pending Abort offer from opponent */
11670
11671     if (appData.icsActive) {
11672         SendToICS(ics_prefix);
11673         SendToICS("abort\n");
11674     } else {
11675         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11676     }
11677 }
11678
11679 void
11680 ResignEvent()
11681 {
11682     /* Resign.  You can do this even if it's not your turn. */
11683
11684     if (appData.icsActive) {
11685         SendToICS(ics_prefix);
11686         SendToICS("resign\n");
11687     } else {
11688         switch (gameMode) {
11689           case MachinePlaysWhite:
11690             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11691             break;
11692           case MachinePlaysBlack:
11693             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11694             break;
11695           case EditGame:
11696             if (cmailMsgLoaded) {
11697                 TruncateGame();
11698                 if (WhiteOnMove(cmailOldMove)) {
11699                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11700                 } else {
11701                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11702                 }
11703                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11704             }
11705             break;
11706           default:
11707             break;
11708         }
11709     }
11710 }
11711
11712
11713 void
11714 StopObservingEvent()
11715 {
11716     /* Stop observing current games */
11717     SendToICS(ics_prefix);
11718     SendToICS("unobserve\n");
11719 }
11720
11721 void
11722 StopExaminingEvent()
11723 {
11724     /* Stop observing current game */
11725     SendToICS(ics_prefix);
11726     SendToICS("unexamine\n");
11727 }
11728
11729 void
11730 ForwardInner(target)
11731      int target;
11732 {
11733     int limit;
11734
11735     if (appData.debugMode)
11736         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11737                 target, currentMove, forwardMostMove);
11738
11739     if (gameMode == EditPosition)
11740       return;
11741
11742     if (gameMode == PlayFromGameFile && !pausing)
11743       PauseEvent();
11744
11745     if (gameMode == IcsExamining && pausing)
11746       limit = pauseExamForwardMostMove;
11747     else
11748       limit = forwardMostMove;
11749
11750     if (target > limit) target = limit;
11751
11752     if (target > 0 && moveList[target - 1][0]) {
11753         int fromX, fromY, toX, toY;
11754         toX = moveList[target - 1][2] - AAA;
11755         toY = moveList[target - 1][3] - ONE;
11756         if (moveList[target - 1][1] == '@') {
11757             if (appData.highlightLastMove) {
11758                 SetHighlights(-1, -1, toX, toY);
11759             }
11760         } else {
11761             fromX = moveList[target - 1][0] - AAA;
11762             fromY = moveList[target - 1][1] - ONE;
11763             if (target == currentMove + 1) {
11764                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11765             }
11766             if (appData.highlightLastMove) {
11767                 SetHighlights(fromX, fromY, toX, toY);
11768             }
11769         }
11770     }
11771     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11772         gameMode == Training || gameMode == PlayFromGameFile ||
11773         gameMode == AnalyzeFile) {
11774         while (currentMove < target) {
11775             SendMoveToProgram(currentMove++, &first);
11776         }
11777     } else {
11778         currentMove = target;
11779     }
11780
11781     if (gameMode == EditGame || gameMode == EndOfGame) {
11782         whiteTimeRemaining = timeRemaining[0][currentMove];
11783         blackTimeRemaining = timeRemaining[1][currentMove];
11784     }
11785     DisplayBothClocks();
11786     DisplayMove(currentMove - 1);
11787     DrawPosition(FALSE, boards[currentMove]);
11788     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11789     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11790         DisplayComment(currentMove - 1, commentList[currentMove]);
11791     }
11792 }
11793
11794
11795 void
11796 ForwardEvent()
11797 {
11798     if (gameMode == IcsExamining && !pausing) {
11799         SendToICS(ics_prefix);
11800         SendToICS("forward\n");
11801     } else {
11802         ForwardInner(currentMove + 1);
11803     }
11804 }
11805
11806 void
11807 ToEndEvent()
11808 {
11809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11810         /* to optimze, we temporarily turn off analysis mode while we feed
11811          * the remaining moves to the engine. Otherwise we get analysis output
11812          * after each move.
11813          */
11814         if (first.analysisSupport) {
11815           SendToProgram("exit\nforce\n", &first);
11816           first.analyzing = FALSE;
11817         }
11818     }
11819
11820     if (gameMode == IcsExamining && !pausing) {
11821         SendToICS(ics_prefix);
11822         SendToICS("forward 999999\n");
11823     } else {
11824         ForwardInner(forwardMostMove);
11825     }
11826
11827     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11828         /* we have fed all the moves, so reactivate analysis mode */
11829         SendToProgram("analyze\n", &first);
11830         first.analyzing = TRUE;
11831         /*first.maybeThinking = TRUE;*/
11832         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11833     }
11834 }
11835
11836 void
11837 BackwardInner(target)
11838      int target;
11839 {
11840     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11841
11842     if (appData.debugMode)
11843         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11844                 target, currentMove, forwardMostMove);
11845
11846     if (gameMode == EditPosition) return;
11847     if (currentMove <= backwardMostMove) {
11848         ClearHighlights();
11849         DrawPosition(full_redraw, boards[currentMove]);
11850         return;
11851     }
11852     if (gameMode == PlayFromGameFile && !pausing)
11853       PauseEvent();
11854
11855     if (moveList[target][0]) {
11856         int fromX, fromY, toX, toY;
11857         toX = moveList[target][2] - AAA;
11858         toY = moveList[target][3] - ONE;
11859         if (moveList[target][1] == '@') {
11860             if (appData.highlightLastMove) {
11861                 SetHighlights(-1, -1, toX, toY);
11862             }
11863         } else {
11864             fromX = moveList[target][0] - AAA;
11865             fromY = moveList[target][1] - ONE;
11866             if (target == currentMove - 1) {
11867                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11868             }
11869             if (appData.highlightLastMove) {
11870                 SetHighlights(fromX, fromY, toX, toY);
11871             }
11872         }
11873     }
11874     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11875         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11876         while (currentMove > target) {
11877             SendToProgram("undo\n", &first);
11878             currentMove--;
11879         }
11880     } else {
11881         currentMove = target;
11882     }
11883
11884     if (gameMode == EditGame || gameMode == EndOfGame) {
11885         whiteTimeRemaining = timeRemaining[0][currentMove];
11886         blackTimeRemaining = timeRemaining[1][currentMove];
11887     }
11888     DisplayBothClocks();
11889     DisplayMove(currentMove - 1);
11890     DrawPosition(full_redraw, boards[currentMove]);
11891     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11892     // [HGM] PV info: routine tests if comment empty
11893     DisplayComment(currentMove - 1, commentList[currentMove]);
11894 }
11895
11896 void
11897 BackwardEvent()
11898 {
11899     if (gameMode == IcsExamining && !pausing) {
11900         SendToICS(ics_prefix);
11901         SendToICS("backward\n");
11902     } else {
11903         BackwardInner(currentMove - 1);
11904     }
11905 }
11906
11907 void
11908 ToStartEvent()
11909 {
11910     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11911         /* to optimze, we temporarily turn off analysis mode while we undo
11912          * all the moves. Otherwise we get analysis output after each undo.
11913          */
11914         if (first.analysisSupport) {
11915           SendToProgram("exit\nforce\n", &first);
11916           first.analyzing = FALSE;
11917         }
11918     }
11919
11920     if (gameMode == IcsExamining && !pausing) {
11921         SendToICS(ics_prefix);
11922         SendToICS("backward 999999\n");
11923     } else {
11924         BackwardInner(backwardMostMove);
11925     }
11926
11927     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11928         /* we have fed all the moves, so reactivate analysis mode */
11929         SendToProgram("analyze\n", &first);
11930         first.analyzing = TRUE;
11931         /*first.maybeThinking = TRUE;*/
11932         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11933     }
11934 }
11935
11936 void
11937 ToNrEvent(int to)
11938 {
11939   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11940   if (to >= forwardMostMove) to = forwardMostMove;
11941   if (to <= backwardMostMove) to = backwardMostMove;
11942   if (to < currentMove) {
11943     BackwardInner(to);
11944   } else {
11945     ForwardInner(to);
11946   }
11947 }
11948
11949 void
11950 RevertEvent()
11951 {
11952     if (gameMode != IcsExamining) {
11953         DisplayError(_("You are not examining a game"), 0);
11954         return;
11955     }
11956     if (pausing) {
11957         DisplayError(_("You can't revert while pausing"), 0);
11958         return;
11959     }
11960     SendToICS(ics_prefix);
11961     SendToICS("revert\n");
11962 }
11963
11964 void
11965 RetractMoveEvent()
11966 {
11967     switch (gameMode) {
11968       case MachinePlaysWhite:
11969       case MachinePlaysBlack:
11970         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11971             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11972             return;
11973         }
11974         if (forwardMostMove < 2) return;
11975         currentMove = forwardMostMove = forwardMostMove - 2;
11976         whiteTimeRemaining = timeRemaining[0][currentMove];
11977         blackTimeRemaining = timeRemaining[1][currentMove];
11978         DisplayBothClocks();
11979         DisplayMove(currentMove - 1);
11980         ClearHighlights();/*!! could figure this out*/
11981         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11982         SendToProgram("remove\n", &first);
11983         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11984         break;
11985
11986       case BeginningOfGame:
11987       default:
11988         break;
11989
11990       case IcsPlayingWhite:
11991       case IcsPlayingBlack:
11992         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11993             SendToICS(ics_prefix);
11994             SendToICS("takeback 2\n");
11995         } else {
11996             SendToICS(ics_prefix);
11997             SendToICS("takeback 1\n");
11998         }
11999         break;
12000     }
12001 }
12002
12003 void
12004 MoveNowEvent()
12005 {
12006     ChessProgramState *cps;
12007
12008     switch (gameMode) {
12009       case MachinePlaysWhite:
12010         if (!WhiteOnMove(forwardMostMove)) {
12011             DisplayError(_("It is your turn"), 0);
12012             return;
12013         }
12014         cps = &first;
12015         break;
12016       case MachinePlaysBlack:
12017         if (WhiteOnMove(forwardMostMove)) {
12018             DisplayError(_("It is your turn"), 0);
12019             return;
12020         }
12021         cps = &first;
12022         break;
12023       case TwoMachinesPlay:
12024         if (WhiteOnMove(forwardMostMove) ==
12025             (first.twoMachinesColor[0] == 'w')) {
12026             cps = &first;
12027         } else {
12028             cps = &second;
12029         }
12030         break;
12031       case BeginningOfGame:
12032       default:
12033         return;
12034     }
12035     SendToProgram("?\n", cps);
12036 }
12037
12038 void
12039 TruncateGameEvent()
12040 {
12041     EditGameEvent();
12042     if (gameMode != EditGame) return;
12043     TruncateGame();
12044 }
12045
12046 void
12047 TruncateGame()
12048 {
12049     if (forwardMostMove > currentMove) {
12050         if (gameInfo.resultDetails != NULL) {
12051             free(gameInfo.resultDetails);
12052             gameInfo.resultDetails = NULL;
12053             gameInfo.result = GameUnfinished;
12054         }
12055         forwardMostMove = currentMove;
12056         HistorySet(parseList, backwardMostMove, forwardMostMove,
12057                    currentMove-1);
12058     }
12059 }
12060
12061 void
12062 HintEvent()
12063 {
12064     if (appData.noChessProgram) return;
12065     switch (gameMode) {
12066       case MachinePlaysWhite:
12067         if (WhiteOnMove(forwardMostMove)) {
12068             DisplayError(_("Wait until your turn"), 0);
12069             return;
12070         }
12071         break;
12072       case BeginningOfGame:
12073       case MachinePlaysBlack:
12074         if (!WhiteOnMove(forwardMostMove)) {
12075             DisplayError(_("Wait until your turn"), 0);
12076             return;
12077         }
12078         break;
12079       default:
12080         DisplayError(_("No hint available"), 0);
12081         return;
12082     }
12083     SendToProgram("hint\n", &first);
12084     hintRequested = TRUE;
12085 }
12086
12087 void
12088 BookEvent()
12089 {
12090     if (appData.noChessProgram) return;
12091     switch (gameMode) {
12092       case MachinePlaysWhite:
12093         if (WhiteOnMove(forwardMostMove)) {
12094             DisplayError(_("Wait until your turn"), 0);
12095             return;
12096         }
12097         break;
12098       case BeginningOfGame:
12099       case MachinePlaysBlack:
12100         if (!WhiteOnMove(forwardMostMove)) {
12101             DisplayError(_("Wait until your turn"), 0);
12102             return;
12103         }
12104         break;
12105       case EditPosition:
12106         EditPositionDone();
12107         break;
12108       case TwoMachinesPlay:
12109         return;
12110       default:
12111         break;
12112     }
12113     SendToProgram("bk\n", &first);
12114     bookOutput[0] = NULLCHAR;
12115     bookRequested = TRUE;
12116 }
12117
12118 void
12119 AboutGameEvent()
12120 {
12121     char *tags = PGNTags(&gameInfo);
12122     TagsPopUp(tags, CmailMsg());
12123     free(tags);
12124 }
12125
12126 /* end button procedures */
12127
12128 void
12129 PrintPosition(fp, move)
12130      FILE *fp;
12131      int move;
12132 {
12133     int i, j;
12134
12135     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12136         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12137             char c = PieceToChar(boards[move][i][j]);
12138             fputc(c == 'x' ? '.' : c, fp);
12139             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12140         }
12141     }
12142     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12143       fprintf(fp, "white to play\n");
12144     else
12145       fprintf(fp, "black to play\n");
12146 }
12147
12148 void
12149 PrintOpponents(fp)
12150      FILE *fp;
12151 {
12152     if (gameInfo.white != NULL) {
12153         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12154     } else {
12155         fprintf(fp, "\n");
12156     }
12157 }
12158
12159 /* Find last component of program's own name, using some heuristics */
12160 void
12161 TidyProgramName(prog, host, buf)
12162      char *prog, *host, buf[MSG_SIZ];
12163 {
12164     char *p, *q;
12165     int local = (strcmp(host, "localhost") == 0);
12166     while (!local && (p = strchr(prog, ';')) != NULL) {
12167         p++;
12168         while (*p == ' ') p++;
12169         prog = p;
12170     }
12171     if (*prog == '"' || *prog == '\'') {
12172         q = strchr(prog + 1, *prog);
12173     } else {
12174         q = strchr(prog, ' ');
12175     }
12176     if (q == NULL) q = prog + strlen(prog);
12177     p = q;
12178     while (p >= prog && *p != '/' && *p != '\\') p--;
12179     p++;
12180     if(p == prog && *p == '"') p++;
12181     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12182     memcpy(buf, p, q - p);
12183     buf[q - p] = NULLCHAR;
12184     if (!local) {
12185         strcat(buf, "@");
12186         strcat(buf, host);
12187     }
12188 }
12189
12190 char *
12191 TimeControlTagValue()
12192 {
12193     char buf[MSG_SIZ];
12194     if (!appData.clockMode) {
12195         strcpy(buf, "-");
12196     } else if (movesPerSession > 0) {
12197         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12198     } else if (timeIncrement == 0) {
12199         sprintf(buf, "%ld", timeControl/1000);
12200     } else {
12201         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12202     }
12203     return StrSave(buf);
12204 }
12205
12206 void
12207 SetGameInfo()
12208 {
12209     /* This routine is used only for certain modes */
12210     VariantClass v = gameInfo.variant;
12211     ClearGameInfo(&gameInfo);
12212     gameInfo.variant = v;
12213
12214     switch (gameMode) {
12215       case MachinePlaysWhite:
12216         gameInfo.event = StrSave( appData.pgnEventHeader );
12217         gameInfo.site = StrSave(HostName());
12218         gameInfo.date = PGNDate();
12219         gameInfo.round = StrSave("-");
12220         gameInfo.white = StrSave(first.tidy);
12221         gameInfo.black = StrSave(UserName());
12222         gameInfo.timeControl = TimeControlTagValue();
12223         break;
12224
12225       case MachinePlaysBlack:
12226         gameInfo.event = StrSave( appData.pgnEventHeader );
12227         gameInfo.site = StrSave(HostName());
12228         gameInfo.date = PGNDate();
12229         gameInfo.round = StrSave("-");
12230         gameInfo.white = StrSave(UserName());
12231         gameInfo.black = StrSave(first.tidy);
12232         gameInfo.timeControl = TimeControlTagValue();
12233         break;
12234
12235       case TwoMachinesPlay:
12236         gameInfo.event = StrSave( appData.pgnEventHeader );
12237         gameInfo.site = StrSave(HostName());
12238         gameInfo.date = PGNDate();
12239         if (matchGame > 0) {
12240             char buf[MSG_SIZ];
12241             sprintf(buf, "%d", matchGame);
12242             gameInfo.round = StrSave(buf);
12243         } else {
12244             gameInfo.round = StrSave("-");
12245         }
12246         if (first.twoMachinesColor[0] == 'w') {
12247             gameInfo.white = StrSave(first.tidy);
12248             gameInfo.black = StrSave(second.tidy);
12249         } else {
12250             gameInfo.white = StrSave(second.tidy);
12251             gameInfo.black = StrSave(first.tidy);
12252         }
12253         gameInfo.timeControl = TimeControlTagValue();
12254         break;
12255
12256       case EditGame:
12257         gameInfo.event = StrSave("Edited game");
12258         gameInfo.site = StrSave(HostName());
12259         gameInfo.date = PGNDate();
12260         gameInfo.round = StrSave("-");
12261         gameInfo.white = StrSave("-");
12262         gameInfo.black = StrSave("-");
12263         break;
12264
12265       case EditPosition:
12266         gameInfo.event = StrSave("Edited position");
12267         gameInfo.site = StrSave(HostName());
12268         gameInfo.date = PGNDate();
12269         gameInfo.round = StrSave("-");
12270         gameInfo.white = StrSave("-");
12271         gameInfo.black = StrSave("-");
12272         break;
12273
12274       case IcsPlayingWhite:
12275       case IcsPlayingBlack:
12276       case IcsObserving:
12277       case IcsExamining:
12278         break;
12279
12280       case PlayFromGameFile:
12281         gameInfo.event = StrSave("Game from non-PGN file");
12282         gameInfo.site = StrSave(HostName());
12283         gameInfo.date = PGNDate();
12284         gameInfo.round = StrSave("-");
12285         gameInfo.white = StrSave("?");
12286         gameInfo.black = StrSave("?");
12287         break;
12288
12289       default:
12290         break;
12291     }
12292 }
12293
12294 void
12295 ReplaceComment(index, text)
12296      int index;
12297      char *text;
12298 {
12299     int len;
12300
12301     while (*text == '\n') text++;
12302     len = strlen(text);
12303     while (len > 0 && text[len - 1] == '\n') len--;
12304
12305     if (commentList[index] != NULL)
12306       free(commentList[index]);
12307
12308     if (len == 0) {
12309         commentList[index] = NULL;
12310         return;
12311     }
12312     commentList[index] = (char *) malloc(len + 2);
12313     strncpy(commentList[index], text, len);
12314     commentList[index][len] = '\n';
12315     commentList[index][len + 1] = NULLCHAR;
12316 }
12317
12318 void
12319 CrushCRs(text)
12320      char *text;
12321 {
12322   char *p = text;
12323   char *q = text;
12324   char ch;
12325
12326   do {
12327     ch = *p++;
12328     if (ch == '\r') continue;
12329     *q++ = ch;
12330   } while (ch != '\0');
12331 }
12332
12333 void
12334 AppendComment(index, text)
12335      int index;
12336      char *text;
12337 {
12338     int oldlen, len;
12339     char *old;
12340
12341     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12342
12343     CrushCRs(text);
12344     while (*text == '\n') text++;
12345     len = strlen(text);
12346     while (len > 0 && text[len - 1] == '\n') len--;
12347
12348     if (len == 0) return;
12349
12350     if (commentList[index] != NULL) {
12351         old = commentList[index];
12352         oldlen = strlen(old);
12353         commentList[index] = (char *) malloc(oldlen + len + 2);
12354         strcpy(commentList[index], old);
12355         free(old);
12356         strncpy(&commentList[index][oldlen], text, len);
12357         commentList[index][oldlen + len] = '\n';
12358         commentList[index][oldlen + len + 1] = NULLCHAR;
12359     } else {
12360         commentList[index] = (char *) malloc(len + 2);
12361         strncpy(commentList[index], text, len);
12362         commentList[index][len] = '\n';
12363         commentList[index][len + 1] = NULLCHAR;
12364     }
12365 }
12366
12367 static char * FindStr( char * text, char * sub_text )
12368 {
12369     char * result = strstr( text, sub_text );
12370
12371     if( result != NULL ) {
12372         result += strlen( sub_text );
12373     }
12374
12375     return result;
12376 }
12377
12378 /* [AS] Try to extract PV info from PGN comment */
12379 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12380 char *GetInfoFromComment( int index, char * text )
12381 {
12382     char * sep = text;
12383
12384     if( text != NULL && index > 0 ) {
12385         int score = 0;
12386         int depth = 0;
12387         int time = -1, sec = 0, deci;
12388         char * s_eval = FindStr( text, "[%eval " );
12389         char * s_emt = FindStr( text, "[%emt " );
12390
12391         if( s_eval != NULL || s_emt != NULL ) {
12392             /* New style */
12393             char delim;
12394
12395             if( s_eval != NULL ) {
12396                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12397                     return text;
12398                 }
12399
12400                 if( delim != ']' ) {
12401                     return text;
12402                 }
12403             }
12404
12405             if( s_emt != NULL ) {
12406             }
12407         }
12408         else {
12409             /* We expect something like: [+|-]nnn.nn/dd */
12410             int score_lo = 0;
12411
12412             sep = strchr( text, '/' );
12413             if( sep == NULL || sep < (text+4) ) {
12414                 return text;
12415             }
12416
12417             time = -1; sec = -1; deci = -1;
12418             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12419                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12420                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12421                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12422                 return text;
12423             }
12424
12425             if( score_lo < 0 || score_lo >= 100 ) {
12426                 return text;
12427             }
12428
12429             if(sec >= 0) time = 600*time + 10*sec; else
12430             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12431
12432             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12433
12434             /* [HGM] PV time: now locate end of PV info */
12435             while( *++sep >= '0' && *sep <= '9'); // strip depth
12436             if(time >= 0)
12437             while( *++sep >= '0' && *sep <= '9'); // strip time
12438             if(sec >= 0)
12439             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12440             if(deci >= 0)
12441             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12442             while(*sep == ' ') sep++;
12443         }
12444
12445         if( depth <= 0 ) {
12446             return text;
12447         }
12448
12449         if( time < 0 ) {
12450             time = -1;
12451         }
12452
12453         pvInfoList[index-1].depth = depth;
12454         pvInfoList[index-1].score = score;
12455         pvInfoList[index-1].time  = 10*time; // centi-sec
12456     }
12457     return sep;
12458 }
12459
12460 void
12461 SendToProgram(message, cps)
12462      char *message;
12463      ChessProgramState *cps;
12464 {
12465     int count, outCount, error;
12466     char buf[MSG_SIZ];
12467
12468     if (cps->pr == NULL) return;
12469     Attention(cps);
12470
12471     if (appData.debugMode) {
12472         TimeMark now;
12473         GetTimeMark(&now);
12474         fprintf(debugFP, "%ld >%-6s: %s",
12475                 SubtractTimeMarks(&now, &programStartTime),
12476                 cps->which, message);
12477     }
12478
12479     count = strlen(message);
12480     outCount = OutputToProcess(cps->pr, message, count, &error);
12481     if (outCount < count && !exiting
12482                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12483         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12484         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12485             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12486                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12487                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12488             } else {
12489                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12490             }
12491             gameInfo.resultDetails = buf;
12492         }
12493         DisplayFatalError(buf, error, 1);
12494     }
12495 }
12496
12497 void
12498 ReceiveFromProgram(isr, closure, message, count, error)
12499      InputSourceRef isr;
12500      VOIDSTAR closure;
12501      char *message;
12502      int count;
12503      int error;
12504 {
12505     char *end_str;
12506     char buf[MSG_SIZ];
12507     ChessProgramState *cps = (ChessProgramState *)closure;
12508
12509     if (isr != cps->isr) return; /* Killed intentionally */
12510     if (count <= 0) {
12511         if (count == 0) {
12512             sprintf(buf,
12513                     _("Error: %s chess program (%s) exited unexpectedly"),
12514                     cps->which, cps->program);
12515         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12516                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12517                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12518                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12519                 } else {
12520                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12521                 }
12522                 gameInfo.resultDetails = buf;
12523             }
12524             RemoveInputSource(cps->isr);
12525             DisplayFatalError(buf, 0, 1);
12526         } else {
12527             sprintf(buf,
12528                     _("Error reading from %s chess program (%s)"),
12529                     cps->which, cps->program);
12530             RemoveInputSource(cps->isr);
12531
12532             /* [AS] Program is misbehaving badly... kill it */
12533             if( count == -2 ) {
12534                 DestroyChildProcess( cps->pr, 9 );
12535                 cps->pr = NoProc;
12536             }
12537
12538             DisplayFatalError(buf, error, 1);
12539         }
12540         return;
12541     }
12542
12543     if ((end_str = strchr(message, '\r')) != NULL)
12544       *end_str = NULLCHAR;
12545     if ((end_str = strchr(message, '\n')) != NULL)
12546       *end_str = NULLCHAR;
12547
12548     if (appData.debugMode) {
12549         TimeMark now; int print = 1;
12550         char *quote = ""; char c; int i;
12551
12552         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12553                 char start = message[0];
12554                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12555                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12556                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12557                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12558                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12559                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12560                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12561                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12562                         { quote = "# "; print = (appData.engineComments == 2); }
12563                 message[0] = start; // restore original message
12564         }
12565         if(print) {
12566                 GetTimeMark(&now);
12567                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12568                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12569                         quote,
12570                         message);
12571         }
12572     }
12573
12574     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12575     if (appData.icsEngineAnalyze) {
12576         if (strstr(message, "whisper") != NULL ||
12577              strstr(message, "kibitz") != NULL ||
12578             strstr(message, "tellics") != NULL) return;
12579     }
12580
12581     HandleMachineMove(message, cps);
12582 }
12583
12584
12585 void
12586 SendTimeControl(cps, mps, tc, inc, sd, st)
12587      ChessProgramState *cps;
12588      int mps, inc, sd, st;
12589      long tc;
12590 {
12591     char buf[MSG_SIZ];
12592     int seconds;
12593
12594     if( timeControl_2 > 0 ) {
12595         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12596             tc = timeControl_2;
12597         }
12598     }
12599     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12600     inc /= cps->timeOdds;
12601     st  /= cps->timeOdds;
12602
12603     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12604
12605     if (st > 0) {
12606       /* Set exact time per move, normally using st command */
12607       if (cps->stKludge) {
12608         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12609         seconds = st % 60;
12610         if (seconds == 0) {
12611           sprintf(buf, "level 1 %d\n", st/60);
12612         } else {
12613           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12614         }
12615       } else {
12616         sprintf(buf, "st %d\n", st);
12617       }
12618     } else {
12619       /* Set conventional or incremental time control, using level command */
12620       if (seconds == 0) {
12621         /* Note old gnuchess bug -- minutes:seconds used to not work.
12622            Fixed in later versions, but still avoid :seconds
12623            when seconds is 0. */
12624         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12625       } else {
12626         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12627                 seconds, inc/1000);
12628       }
12629     }
12630     SendToProgram(buf, cps);
12631
12632     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12633     /* Orthogonally, limit search to given depth */
12634     if (sd > 0) {
12635       if (cps->sdKludge) {
12636         sprintf(buf, "depth\n%d\n", sd);
12637       } else {
12638         sprintf(buf, "sd %d\n", sd);
12639       }
12640       SendToProgram(buf, cps);
12641     }
12642
12643     if(cps->nps > 0) { /* [HGM] nps */
12644         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12645         else {
12646                 sprintf(buf, "nps %d\n", cps->nps);
12647               SendToProgram(buf, cps);
12648         }
12649     }
12650 }
12651
12652 ChessProgramState *WhitePlayer()
12653 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12654 {
12655     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12656        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12657         return &second;
12658     return &first;
12659 }
12660
12661 void
12662 SendTimeRemaining(cps, machineWhite)
12663      ChessProgramState *cps;
12664      int /*boolean*/ machineWhite;
12665 {
12666     char message[MSG_SIZ];
12667     long time, otime;
12668
12669     /* Note: this routine must be called when the clocks are stopped
12670        or when they have *just* been set or switched; otherwise
12671        it will be off by the time since the current tick started.
12672     */
12673     if (machineWhite) {
12674         time = whiteTimeRemaining / 10;
12675         otime = blackTimeRemaining / 10;
12676     } else {
12677         time = blackTimeRemaining / 10;
12678         otime = whiteTimeRemaining / 10;
12679     }
12680     /* [HGM] translate opponent's time by time-odds factor */
12681     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12682     if (appData.debugMode) {
12683         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12684     }
12685
12686     if (time <= 0) time = 1;
12687     if (otime <= 0) otime = 1;
12688
12689     sprintf(message, "time %ld\n", time);
12690     SendToProgram(message, cps);
12691
12692     sprintf(message, "otim %ld\n", otime);
12693     SendToProgram(message, cps);
12694 }
12695
12696 int
12697 BoolFeature(p, name, loc, cps)
12698      char **p;
12699      char *name;
12700      int *loc;
12701      ChessProgramState *cps;
12702 {
12703   char buf[MSG_SIZ];
12704   int len = strlen(name);
12705   int val;
12706   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12707     (*p) += len + 1;
12708     sscanf(*p, "%d", &val);
12709     *loc = (val != 0);
12710     while (**p && **p != ' ') (*p)++;
12711     sprintf(buf, "accepted %s\n", name);
12712     SendToProgram(buf, cps);
12713     return TRUE;
12714   }
12715   return FALSE;
12716 }
12717
12718 int
12719 IntFeature(p, name, loc, cps)
12720      char **p;
12721      char *name;
12722      int *loc;
12723      ChessProgramState *cps;
12724 {
12725   char buf[MSG_SIZ];
12726   int len = strlen(name);
12727   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12728     (*p) += len + 1;
12729     sscanf(*p, "%d", loc);
12730     while (**p && **p != ' ') (*p)++;
12731     sprintf(buf, "accepted %s\n", name);
12732     SendToProgram(buf, cps);
12733     return TRUE;
12734   }
12735   return FALSE;
12736 }
12737
12738 int
12739 StringFeature(p, name, loc, cps)
12740      char **p;
12741      char *name;
12742      char loc[];
12743      ChessProgramState *cps;
12744 {
12745   char buf[MSG_SIZ];
12746   int len = strlen(name);
12747   if (strncmp((*p), name, len) == 0
12748       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12749     (*p) += len + 2;
12750     sscanf(*p, "%[^\"]", loc);
12751     while (**p && **p != '\"') (*p)++;
12752     if (**p == '\"') (*p)++;
12753     sprintf(buf, "accepted %s\n", name);
12754     SendToProgram(buf, cps);
12755     return TRUE;
12756   }
12757   return FALSE;
12758 }
12759
12760 int
12761 ParseOption(Option *opt, ChessProgramState *cps)
12762 // [HGM] options: process the string that defines an engine option, and determine
12763 // name, type, default value, and allowed value range
12764 {
12765         char *p, *q, buf[MSG_SIZ];
12766         int n, min = (-1)<<31, max = 1<<31, def;
12767
12768         if(p = strstr(opt->name, " -spin ")) {
12769             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12770             if(max < min) max = min; // enforce consistency
12771             if(def < min) def = min;
12772             if(def > max) def = max;
12773             opt->value = def;
12774             opt->min = min;
12775             opt->max = max;
12776             opt->type = Spin;
12777         } else if((p = strstr(opt->name, " -slider "))) {
12778             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12779             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12780             if(max < min) max = min; // enforce consistency
12781             if(def < min) def = min;
12782             if(def > max) def = max;
12783             opt->value = def;
12784             opt->min = min;
12785             opt->max = max;
12786             opt->type = Spin; // Slider;
12787         } else if((p = strstr(opt->name, " -string "))) {
12788             opt->textValue = p+9;
12789             opt->type = TextBox;
12790         } else if((p = strstr(opt->name, " -file "))) {
12791             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12792             opt->textValue = p+7;
12793             opt->type = TextBox; // FileName;
12794         } else if((p = strstr(opt->name, " -path "))) {
12795             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12796             opt->textValue = p+7;
12797             opt->type = TextBox; // PathName;
12798         } else if(p = strstr(opt->name, " -check ")) {
12799             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12800             opt->value = (def != 0);
12801             opt->type = CheckBox;
12802         } else if(p = strstr(opt->name, " -combo ")) {
12803             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12804             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12805             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12806             opt->value = n = 0;
12807             while(q = StrStr(q, " /// ")) {
12808                 n++; *q = 0;    // count choices, and null-terminate each of them
12809                 q += 5;
12810                 if(*q == '*') { // remember default, which is marked with * prefix
12811                     q++;
12812                     opt->value = n;
12813                 }
12814                 cps->comboList[cps->comboCnt++] = q;
12815             }
12816             cps->comboList[cps->comboCnt++] = NULL;
12817             opt->max = n + 1;
12818             opt->type = ComboBox;
12819         } else if(p = strstr(opt->name, " -button")) {
12820             opt->type = Button;
12821         } else if(p = strstr(opt->name, " -save")) {
12822             opt->type = SaveButton;
12823         } else return FALSE;
12824         *p = 0; // terminate option name
12825         // now look if the command-line options define a setting for this engine option.
12826         if(cps->optionSettings && cps->optionSettings[0])
12827             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12828         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12829                 sprintf(buf, "option %s", p);
12830                 if(p = strstr(buf, ",")) *p = 0;
12831                 strcat(buf, "\n");
12832                 SendToProgram(buf, cps);
12833         }
12834         return TRUE;
12835 }
12836
12837 void
12838 FeatureDone(cps, val)
12839      ChessProgramState* cps;
12840      int val;
12841 {
12842   DelayedEventCallback cb = GetDelayedEvent();
12843   if ((cb == InitBackEnd3 && cps == &first) ||
12844       (cb == TwoMachinesEventIfReady && cps == &second)) {
12845     CancelDelayedEvent();
12846     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12847   }
12848   cps->initDone = val;
12849 }
12850
12851 /* Parse feature command from engine */
12852 void
12853 ParseFeatures(args, cps)
12854      char* args;
12855      ChessProgramState *cps;
12856 {
12857   char *p = args;
12858   char *q;
12859   int val;
12860   char buf[MSG_SIZ];
12861
12862   for (;;) {
12863     while (*p == ' ') p++;
12864     if (*p == NULLCHAR) return;
12865
12866     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12867     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12868     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12869     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12870     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12871     if (BoolFeature(&p, "reuse", &val, cps)) {
12872       /* Engine can disable reuse, but can't enable it if user said no */
12873       if (!val) cps->reuse = FALSE;
12874       continue;
12875     }
12876     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12877     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12878       if (gameMode == TwoMachinesPlay) {
12879         DisplayTwoMachinesTitle();
12880       } else {
12881         DisplayTitle("");
12882       }
12883       continue;
12884     }
12885     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12886     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12887     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12888     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12889     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12890     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12891     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12892     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12893     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12894     if (IntFeature(&p, "done", &val, cps)) {
12895       FeatureDone(cps, val);
12896       continue;
12897     }
12898     /* Added by Tord: */
12899     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12900     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12901     /* End of additions by Tord */
12902
12903     /* [HGM] added features: */
12904     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12905     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12906     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12907     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12908     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12909     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12910     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12911         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12912             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12913             SendToProgram(buf, cps);
12914             continue;
12915         }
12916         if(cps->nrOptions >= MAX_OPTIONS) {
12917             cps->nrOptions--;
12918             sprintf(buf, "%s engine has too many options\n", cps->which);
12919             DisplayError(buf, 0);
12920         }
12921         continue;
12922     }
12923     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12924     /* End of additions by HGM */
12925
12926     /* unknown feature: complain and skip */
12927     q = p;
12928     while (*q && *q != '=') q++;
12929     sprintf(buf, "rejected %.*s\n", q-p, p);
12930     SendToProgram(buf, cps);
12931     p = q;
12932     if (*p == '=') {
12933       p++;
12934       if (*p == '\"') {
12935         p++;
12936         while (*p && *p != '\"') p++;
12937         if (*p == '\"') p++;
12938       } else {
12939         while (*p && *p != ' ') p++;
12940       }
12941     }
12942   }
12943
12944 }
12945
12946 void
12947 PeriodicUpdatesEvent(newState)
12948      int newState;
12949 {
12950     if (newState == appData.periodicUpdates)
12951       return;
12952
12953     appData.periodicUpdates=newState;
12954
12955     /* Display type changes, so update it now */
12956 //    DisplayAnalysis();
12957
12958     /* Get the ball rolling again... */
12959     if (newState) {
12960         AnalysisPeriodicEvent(1);
12961         StartAnalysisClock();
12962     }
12963 }
12964
12965 void
12966 PonderNextMoveEvent(newState)
12967      int newState;
12968 {
12969     if (newState == appData.ponderNextMove) return;
12970     if (gameMode == EditPosition) EditPositionDone();
12971     if (newState) {
12972         SendToProgram("hard\n", &first);
12973         if (gameMode == TwoMachinesPlay) {
12974             SendToProgram("hard\n", &second);
12975         }
12976     } else {
12977         SendToProgram("easy\n", &first);
12978         thinkOutput[0] = NULLCHAR;
12979         if (gameMode == TwoMachinesPlay) {
12980             SendToProgram("easy\n", &second);
12981         }
12982     }
12983     appData.ponderNextMove = newState;
12984 }
12985
12986 void
12987 NewSettingEvent(option, command, value)
12988      char *command;
12989      int option, value;
12990 {
12991     char buf[MSG_SIZ];
12992
12993     if (gameMode == EditPosition) EditPositionDone();
12994     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12995     SendToProgram(buf, &first);
12996     if (gameMode == TwoMachinesPlay) {
12997         SendToProgram(buf, &second);
12998     }
12999 }
13000
13001 void
13002 ShowThinkingEvent()
13003 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13004 {
13005     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13006     int newState = appData.showThinking
13007         // [HGM] thinking: other features now need thinking output as well
13008         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13009
13010     if (oldState == newState) return;
13011     oldState = newState;
13012     if (gameMode == EditPosition) EditPositionDone();
13013     if (oldState) {
13014         SendToProgram("post\n", &first);
13015         if (gameMode == TwoMachinesPlay) {
13016             SendToProgram("post\n", &second);
13017         }
13018     } else {
13019         SendToProgram("nopost\n", &first);
13020         thinkOutput[0] = NULLCHAR;
13021         if (gameMode == TwoMachinesPlay) {
13022             SendToProgram("nopost\n", &second);
13023         }
13024     }
13025 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13026 }
13027
13028 void
13029 AskQuestionEvent(title, question, replyPrefix, which)
13030      char *title; char *question; char *replyPrefix; char *which;
13031 {
13032   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13033   if (pr == NoProc) return;
13034   AskQuestion(title, question, replyPrefix, pr);
13035 }
13036
13037 void
13038 DisplayMove(moveNumber)
13039      int moveNumber;
13040 {
13041     char message[MSG_SIZ];
13042     char res[MSG_SIZ];
13043     char cpThinkOutput[MSG_SIZ];
13044
13045     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13046
13047     if (moveNumber == forwardMostMove - 1 ||
13048         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13049
13050         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13051
13052         if (strchr(cpThinkOutput, '\n')) {
13053             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13054         }
13055     } else {
13056         *cpThinkOutput = NULLCHAR;
13057     }
13058
13059     /* [AS] Hide thinking from human user */
13060     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13061         *cpThinkOutput = NULLCHAR;
13062         if( thinkOutput[0] != NULLCHAR ) {
13063             int i;
13064
13065             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13066                 cpThinkOutput[i] = '.';
13067             }
13068             cpThinkOutput[i] = NULLCHAR;
13069             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13070         }
13071     }
13072
13073     if (moveNumber == forwardMostMove - 1 &&
13074         gameInfo.resultDetails != NULL) {
13075         if (gameInfo.resultDetails[0] == NULLCHAR) {
13076             sprintf(res, " %s", PGNResult(gameInfo.result));
13077         } else {
13078             sprintf(res, " {%s} %s",
13079                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13080         }
13081     } else {
13082         res[0] = NULLCHAR;
13083     }
13084
13085     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13086         DisplayMessage(res, cpThinkOutput);
13087     } else {
13088         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13089                 WhiteOnMove(moveNumber) ? " " : ".. ",
13090                 parseList[moveNumber], res);
13091         DisplayMessage(message, cpThinkOutput);
13092     }
13093 }
13094
13095 void
13096 DisplayComment(moveNumber, text)
13097      int moveNumber;
13098      char *text;
13099 {
13100     char title[MSG_SIZ];
13101     char buf[8000]; // comment can be long!
13102     int score, depth;
13103
13104     if( appData.autoDisplayComment ) {
13105         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13106             strcpy(title, "Comment");
13107         } else {
13108             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13109                     WhiteOnMove(moveNumber) ? " " : ".. ",
13110                     parseList[moveNumber]);
13111         }
13112         // [HGM] PV info: display PV info together with (or as) comment
13113         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13114             if(text == NULL) text = "";
13115             score = pvInfoList[moveNumber].score;
13116             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13117                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13118             text = buf;
13119         }
13120     } else title[0] = 0;
13121
13122     if (text != NULL)
13123         CommentPopUp(title, text);
13124 }
13125
13126 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13127  * might be busy thinking or pondering.  It can be omitted if your
13128  * gnuchess is configured to stop thinking immediately on any user
13129  * input.  However, that gnuchess feature depends on the FIONREAD
13130  * ioctl, which does not work properly on some flavors of Unix.
13131  */
13132 void
13133 Attention(cps)
13134      ChessProgramState *cps;
13135 {
13136 #if ATTENTION
13137     if (!cps->useSigint) return;
13138     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13139     switch (gameMode) {
13140       case MachinePlaysWhite:
13141       case MachinePlaysBlack:
13142       case TwoMachinesPlay:
13143       case IcsPlayingWhite:
13144       case IcsPlayingBlack:
13145       case AnalyzeMode:
13146       case AnalyzeFile:
13147         /* Skip if we know it isn't thinking */
13148         if (!cps->maybeThinking) return;
13149         if (appData.debugMode)
13150           fprintf(debugFP, "Interrupting %s\n", cps->which);
13151         InterruptChildProcess(cps->pr);
13152         cps->maybeThinking = FALSE;
13153         break;
13154       default:
13155         break;
13156     }
13157 #endif /*ATTENTION*/
13158 }
13159
13160 int
13161 CheckFlags()
13162 {
13163     if (whiteTimeRemaining <= 0) {
13164         if (!whiteFlag) {
13165             whiteFlag = TRUE;
13166             if (appData.icsActive) {
13167                 if (appData.autoCallFlag &&
13168                     gameMode == IcsPlayingBlack && !blackFlag) {
13169                   SendToICS(ics_prefix);
13170                   SendToICS("flag\n");
13171                 }
13172             } else {
13173                 if (blackFlag) {
13174                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13175                 } else {
13176                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13177                     if (appData.autoCallFlag) {
13178                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13179                         return TRUE;
13180                     }
13181                 }
13182             }
13183         }
13184     }
13185     if (blackTimeRemaining <= 0) {
13186         if (!blackFlag) {
13187             blackFlag = TRUE;
13188             if (appData.icsActive) {
13189                 if (appData.autoCallFlag &&
13190                     gameMode == IcsPlayingWhite && !whiteFlag) {
13191                   SendToICS(ics_prefix);
13192                   SendToICS("flag\n");
13193                 }
13194             } else {
13195                 if (whiteFlag) {
13196                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13197                 } else {
13198                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13199                     if (appData.autoCallFlag) {
13200                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13201                         return TRUE;
13202                     }
13203                 }
13204             }
13205         }
13206     }
13207     return FALSE;
13208 }
13209
13210 void
13211 CheckTimeControl()
13212 {
13213     if (!appData.clockMode || appData.icsActive ||
13214         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13215
13216     /*
13217      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13218      */
13219     if ( !WhiteOnMove(forwardMostMove) )
13220         /* White made time control */
13221         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13222         /* [HGM] time odds: correct new time quota for time odds! */
13223                                             / WhitePlayer()->timeOdds;
13224       else
13225         /* Black made time control */
13226         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13227                                             / WhitePlayer()->other->timeOdds;
13228 }
13229
13230 void
13231 DisplayBothClocks()
13232 {
13233     int wom = gameMode == EditPosition ?
13234       !blackPlaysFirst : WhiteOnMove(currentMove);
13235     DisplayWhiteClock(whiteTimeRemaining, wom);
13236     DisplayBlackClock(blackTimeRemaining, !wom);
13237 }
13238
13239
13240 /* Timekeeping seems to be a portability nightmare.  I think everyone
13241    has ftime(), but I'm really not sure, so I'm including some ifdefs
13242    to use other calls if you don't.  Clocks will be less accurate if
13243    you have neither ftime nor gettimeofday.
13244 */
13245
13246 /* VS 2008 requires the #include outside of the function */
13247 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13248 #include <sys/timeb.h>
13249 #endif
13250
13251 /* Get the current time as a TimeMark */
13252 void
13253 GetTimeMark(tm)
13254      TimeMark *tm;
13255 {
13256 #if HAVE_GETTIMEOFDAY
13257
13258     struct timeval timeVal;
13259     struct timezone timeZone;
13260
13261     gettimeofday(&timeVal, &timeZone);
13262     tm->sec = (long) timeVal.tv_sec;
13263     tm->ms = (int) (timeVal.tv_usec / 1000L);
13264
13265 #else /*!HAVE_GETTIMEOFDAY*/
13266 #if HAVE_FTIME
13267
13268 // include <sys/timeb.h> / moved to just above start of function
13269     struct timeb timeB;
13270
13271     ftime(&timeB);
13272     tm->sec = (long) timeB.time;
13273     tm->ms = (int) timeB.millitm;
13274
13275 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13276     tm->sec = (long) time(NULL);
13277     tm->ms = 0;
13278 #endif
13279 #endif
13280 }
13281
13282 /* Return the difference in milliseconds between two
13283    time marks.  We assume the difference will fit in a long!
13284 */
13285 long
13286 SubtractTimeMarks(tm2, tm1)
13287      TimeMark *tm2, *tm1;
13288 {
13289     return 1000L*(tm2->sec - tm1->sec) +
13290            (long) (tm2->ms - tm1->ms);
13291 }
13292
13293
13294 /*
13295  * Code to manage the game clocks.
13296  *
13297  * In tournament play, black starts the clock and then white makes a move.
13298  * We give the human user a slight advantage if he is playing white---the
13299  * clocks don't run until he makes his first move, so it takes zero time.
13300  * Also, we don't account for network lag, so we could get out of sync
13301  * with GNU Chess's clock -- but then, referees are always right.
13302  */
13303
13304 static TimeMark tickStartTM;
13305 static long intendedTickLength;
13306
13307 long
13308 NextTickLength(timeRemaining)
13309      long timeRemaining;
13310 {
13311     long nominalTickLength, nextTickLength;
13312
13313     if (timeRemaining > 0L && timeRemaining <= 10000L)
13314       nominalTickLength = 100L;
13315     else
13316       nominalTickLength = 1000L;
13317     nextTickLength = timeRemaining % nominalTickLength;
13318     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13319
13320     return nextTickLength;
13321 }
13322
13323 /* Adjust clock one minute up or down */
13324 void
13325 AdjustClock(Boolean which, int dir)
13326 {
13327     if(which) blackTimeRemaining += 60000*dir;
13328     else      whiteTimeRemaining += 60000*dir;
13329     DisplayBothClocks();
13330 }
13331
13332 /* Stop clocks and reset to a fresh time control */
13333 void
13334 ResetClocks()
13335 {
13336     (void) StopClockTimer();
13337     if (appData.icsActive) {
13338         whiteTimeRemaining = blackTimeRemaining = 0;
13339     } else { /* [HGM] correct new time quote for time odds */
13340         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13341         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13342     }
13343     if (whiteFlag || blackFlag) {
13344         DisplayTitle("");
13345         whiteFlag = blackFlag = FALSE;
13346     }
13347     DisplayBothClocks();
13348 }
13349
13350 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13351
13352 /* Decrement running clock by amount of time that has passed */
13353 void
13354 DecrementClocks()
13355 {
13356     long timeRemaining;
13357     long lastTickLength, fudge;
13358     TimeMark now;
13359
13360     if (!appData.clockMode) return;
13361     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13362
13363     GetTimeMark(&now);
13364
13365     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13366
13367     /* Fudge if we woke up a little too soon */
13368     fudge = intendedTickLength - lastTickLength;
13369     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13370
13371     if (WhiteOnMove(forwardMostMove)) {
13372         if(whiteNPS >= 0) lastTickLength = 0;
13373         timeRemaining = whiteTimeRemaining -= lastTickLength;
13374         DisplayWhiteClock(whiteTimeRemaining - fudge,
13375                           WhiteOnMove(currentMove));
13376     } else {
13377         if(blackNPS >= 0) lastTickLength = 0;
13378         timeRemaining = blackTimeRemaining -= lastTickLength;
13379         DisplayBlackClock(blackTimeRemaining - fudge,
13380                           !WhiteOnMove(currentMove));
13381     }
13382
13383     if (CheckFlags()) return;
13384
13385     tickStartTM = now;
13386     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13387     StartClockTimer(intendedTickLength);
13388
13389     /* if the time remaining has fallen below the alarm threshold, sound the
13390      * alarm. if the alarm has sounded and (due to a takeback or time control
13391      * with increment) the time remaining has increased to a level above the
13392      * threshold, reset the alarm so it can sound again.
13393      */
13394
13395     if (appData.icsActive && appData.icsAlarm) {
13396
13397         /* make sure we are dealing with the user's clock */
13398         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13399                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13400            )) return;
13401
13402         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13403             alarmSounded = FALSE;
13404         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13405             PlayAlarmSound();
13406             alarmSounded = TRUE;
13407         }
13408     }
13409 }
13410
13411
13412 /* A player has just moved, so stop the previously running
13413    clock and (if in clock mode) start the other one.
13414    We redisplay both clocks in case we're in ICS mode, because
13415    ICS gives us an update to both clocks after every move.
13416    Note that this routine is called *after* forwardMostMove
13417    is updated, so the last fractional tick must be subtracted
13418    from the color that is *not* on move now.
13419 */
13420 void
13421 SwitchClocks()
13422 {
13423     long lastTickLength;
13424     TimeMark now;
13425     int flagged = FALSE;
13426
13427     GetTimeMark(&now);
13428
13429     if (StopClockTimer() && appData.clockMode) {
13430         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13431         if (WhiteOnMove(forwardMostMove)) {
13432             if(blackNPS >= 0) lastTickLength = 0;
13433             blackTimeRemaining -= lastTickLength;
13434            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13435 //         if(pvInfoList[forwardMostMove-1].time == -1)
13436                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13437                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13438         } else {
13439            if(whiteNPS >= 0) lastTickLength = 0;
13440            whiteTimeRemaining -= lastTickLength;
13441            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13442 //         if(pvInfoList[forwardMostMove-1].time == -1)
13443                  pvInfoList[forwardMostMove-1].time =
13444                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13445         }
13446         flagged = CheckFlags();
13447     }
13448     CheckTimeControl();
13449
13450     if (flagged || !appData.clockMode) return;
13451
13452     switch (gameMode) {
13453       case MachinePlaysBlack:
13454       case MachinePlaysWhite:
13455       case BeginningOfGame:
13456         if (pausing) return;
13457         break;
13458
13459       case EditGame:
13460       case PlayFromGameFile:
13461       case IcsExamining:
13462         return;
13463
13464       default:
13465         break;
13466     }
13467
13468     tickStartTM = now;
13469     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13470       whiteTimeRemaining : blackTimeRemaining);
13471     StartClockTimer(intendedTickLength);
13472 }
13473
13474
13475 /* Stop both clocks */
13476 void
13477 StopClocks()
13478 {
13479     long lastTickLength;
13480     TimeMark now;
13481
13482     if (!StopClockTimer()) return;
13483     if (!appData.clockMode) return;
13484
13485     GetTimeMark(&now);
13486
13487     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13488     if (WhiteOnMove(forwardMostMove)) {
13489         if(whiteNPS >= 0) lastTickLength = 0;
13490         whiteTimeRemaining -= lastTickLength;
13491         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13492     } else {
13493         if(blackNPS >= 0) lastTickLength = 0;
13494         blackTimeRemaining -= lastTickLength;
13495         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13496     }
13497     CheckFlags();
13498 }
13499
13500 /* Start clock of player on move.  Time may have been reset, so
13501    if clock is already running, stop and restart it. */
13502 void
13503 StartClocks()
13504 {
13505     (void) StopClockTimer(); /* in case it was running already */
13506     DisplayBothClocks();
13507     if (CheckFlags()) return;
13508
13509     if (!appData.clockMode) return;
13510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13511
13512     GetTimeMark(&tickStartTM);
13513     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13514       whiteTimeRemaining : blackTimeRemaining);
13515
13516    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13517     whiteNPS = blackNPS = -1;
13518     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13519        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13520         whiteNPS = first.nps;
13521     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13522        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13523         blackNPS = first.nps;
13524     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13525         whiteNPS = second.nps;
13526     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13527         blackNPS = second.nps;
13528     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13529
13530     StartClockTimer(intendedTickLength);
13531 }
13532
13533 char *
13534 TimeString(ms)
13535      long ms;
13536 {
13537     long second, minute, hour, day;
13538     char *sign = "";
13539     static char buf[32];
13540
13541     if (ms > 0 && ms <= 9900) {
13542       /* convert milliseconds to tenths, rounding up */
13543       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13544
13545       sprintf(buf, " %03.1f ", tenths/10.0);
13546       return buf;
13547     }
13548
13549     /* convert milliseconds to seconds, rounding up */
13550     /* use floating point to avoid strangeness of integer division
13551        with negative dividends on many machines */
13552     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13553
13554     if (second < 0) {
13555         sign = "-";
13556         second = -second;
13557     }
13558
13559     day = second / (60 * 60 * 24);
13560     second = second % (60 * 60 * 24);
13561     hour = second / (60 * 60);
13562     second = second % (60 * 60);
13563     minute = second / 60;
13564     second = second % 60;
13565
13566     if (day > 0)
13567       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13568               sign, day, hour, minute, second);
13569     else if (hour > 0)
13570       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13571     else
13572       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13573
13574     return buf;
13575 }
13576
13577
13578 /*
13579  * This is necessary because some C libraries aren't ANSI C compliant yet.
13580  */
13581 char *
13582 StrStr(string, match)
13583      char *string, *match;
13584 {
13585     int i, length;
13586
13587     length = strlen(match);
13588
13589     for (i = strlen(string) - length; i >= 0; i--, string++)
13590       if (!strncmp(match, string, length))
13591         return string;
13592
13593     return NULL;
13594 }
13595
13596 char *
13597 StrCaseStr(string, match)
13598      char *string, *match;
13599 {
13600     int i, j, length;
13601
13602     length = strlen(match);
13603
13604     for (i = strlen(string) - length; i >= 0; i--, string++) {
13605         for (j = 0; j < length; j++) {
13606             if (ToLower(match[j]) != ToLower(string[j]))
13607               break;
13608         }
13609         if (j == length) return string;
13610     }
13611
13612     return NULL;
13613 }
13614
13615 #ifndef _amigados
13616 int
13617 StrCaseCmp(s1, s2)
13618      char *s1, *s2;
13619 {
13620     char c1, c2;
13621
13622     for (;;) {
13623         c1 = ToLower(*s1++);
13624         c2 = ToLower(*s2++);
13625         if (c1 > c2) return 1;
13626         if (c1 < c2) return -1;
13627         if (c1 == NULLCHAR) return 0;
13628     }
13629 }
13630
13631
13632 int
13633 ToLower(c)
13634      int c;
13635 {
13636     return isupper(c) ? tolower(c) : c;
13637 }
13638
13639
13640 int
13641 ToUpper(c)
13642      int c;
13643 {
13644     return islower(c) ? toupper(c) : c;
13645 }
13646 #endif /* !_amigados    */
13647
13648 char *
13649 StrSave(s)
13650      char *s;
13651 {
13652     char *ret;
13653
13654     if ((ret = (char *) malloc(strlen(s) + 1))) {
13655         strcpy(ret, s);
13656     }
13657     return ret;
13658 }
13659
13660 char *
13661 StrSavePtr(s, savePtr)
13662      char *s, **savePtr;
13663 {
13664     if (*savePtr) {
13665         free(*savePtr);
13666     }
13667     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13668         strcpy(*savePtr, s);
13669     }
13670     return(*savePtr);
13671 }
13672
13673 char *
13674 PGNDate()
13675 {
13676     time_t clock;
13677     struct tm *tm;
13678     char buf[MSG_SIZ];
13679
13680     clock = time((time_t *)NULL);
13681     tm = localtime(&clock);
13682     sprintf(buf, "%04d.%02d.%02d",
13683             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13684     return StrSave(buf);
13685 }
13686
13687
13688 char *
13689 PositionToFEN(move, overrideCastling)
13690      int move;
13691      char *overrideCastling;
13692 {
13693     int i, j, fromX, fromY, toX, toY;
13694     int whiteToPlay;
13695     char buf[128];
13696     char *p, *q;
13697     int emptycount;
13698     ChessSquare piece;
13699
13700     whiteToPlay = (gameMode == EditPosition) ?
13701       !blackPlaysFirst : (move % 2 == 0);
13702     p = buf;
13703
13704     /* Piece placement data */
13705     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13706         emptycount = 0;
13707         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13708             if (boards[move][i][j] == EmptySquare) {
13709                 emptycount++;
13710             } else { ChessSquare piece = boards[move][i][j];
13711                 if (emptycount > 0) {
13712                     if(emptycount<10) /* [HGM] can be >= 10 */
13713                         *p++ = '0' + emptycount;
13714                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13715                     emptycount = 0;
13716                 }
13717                 if(PieceToChar(piece) == '+') {
13718                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13719                     *p++ = '+';
13720                     piece = (ChessSquare)(DEMOTED piece);
13721                 }
13722                 *p++ = PieceToChar(piece);
13723                 if(p[-1] == '~') {
13724                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13725                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13726                     *p++ = '~';
13727                 }
13728             }
13729         }
13730         if (emptycount > 0) {
13731             if(emptycount<10) /* [HGM] can be >= 10 */
13732                 *p++ = '0' + emptycount;
13733             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13734             emptycount = 0;
13735         }
13736         *p++ = '/';
13737     }
13738     *(p - 1) = ' ';
13739
13740     /* [HGM] print Crazyhouse or Shogi holdings */
13741     if( gameInfo.holdingsWidth ) {
13742         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13743         q = p;
13744         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13745             piece = boards[move][i][BOARD_WIDTH-1];
13746             if( piece != EmptySquare )
13747               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13748                   *p++ = PieceToChar(piece);
13749         }
13750         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13751             piece = boards[move][BOARD_HEIGHT-i-1][0];
13752             if( piece != EmptySquare )
13753               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13754                   *p++ = PieceToChar(piece);
13755         }
13756
13757         if( q == p ) *p++ = '-';
13758         *p++ = ']';
13759         *p++ = ' ';
13760     }
13761
13762     /* Active color */
13763     *p++ = whiteToPlay ? 'w' : 'b';
13764     *p++ = ' ';
13765
13766   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13767     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13768   } else {
13769   if(nrCastlingRights) {
13770      q = p;
13771      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13772        /* [HGM] write directly from rights */
13773            if(castlingRights[move][2] >= 0 &&
13774               castlingRights[move][0] >= 0   )
13775                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13776            if(castlingRights[move][2] >= 0 &&
13777               castlingRights[move][1] >= 0   )
13778                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13779            if(castlingRights[move][5] >= 0 &&
13780               castlingRights[move][3] >= 0   )
13781                 *p++ = castlingRights[move][3] + AAA;
13782            if(castlingRights[move][5] >= 0 &&
13783               castlingRights[move][4] >= 0   )
13784                 *p++ = castlingRights[move][4] + AAA;
13785      } else {
13786
13787         /* [HGM] write true castling rights */
13788         if( nrCastlingRights == 6 ) {
13789             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13790                castlingRights[move][2] >= 0  ) *p++ = 'K';
13791             if(castlingRights[move][1] == BOARD_LEFT &&
13792                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13793             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13794                castlingRights[move][5] >= 0  ) *p++ = 'k';
13795             if(castlingRights[move][4] == BOARD_LEFT &&
13796                castlingRights[move][5] >= 0  ) *p++ = 'q';
13797         }
13798      }
13799      if (q == p) *p++ = '-'; /* No castling rights */
13800      *p++ = ' ';
13801   }
13802
13803   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13804      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13805     /* En passant target square */
13806     if (move > backwardMostMove) {
13807         fromX = moveList[move - 1][0] - AAA;
13808         fromY = moveList[move - 1][1] - ONE;
13809         toX = moveList[move - 1][2] - AAA;
13810         toY = moveList[move - 1][3] - ONE;
13811         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13812             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13813             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13814             fromX == toX) {
13815             /* 2-square pawn move just happened */
13816             *p++ = toX + AAA;
13817             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13818         } else {
13819             *p++ = '-';
13820         }
13821     } else if(move == backwardMostMove) {
13822         // [HGM] perhaps we should always do it like this, and forget the above?
13823         if(epStatus[move] >= 0) {
13824             *p++ = epStatus[move] + AAA;
13825             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13826         } else {
13827             *p++ = '-';
13828         }
13829     } else {
13830         *p++ = '-';
13831     }
13832     *p++ = ' ';
13833   }
13834   }
13835
13836     /* [HGM] find reversible plies */
13837     {   int i = 0, j=move;
13838
13839         if (appData.debugMode) { int k;
13840             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13841             for(k=backwardMostMove; k<=forwardMostMove; k++)
13842                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13843
13844         }
13845
13846         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13847         if( j == backwardMostMove ) i += initialRulePlies;
13848         sprintf(p, "%d ", i);
13849         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13850     }
13851     /* Fullmove number */
13852     sprintf(p, "%d", (move / 2) + 1);
13853
13854     return StrSave(buf);
13855 }
13856
13857 Boolean
13858 ParseFEN(board, blackPlaysFirst, fen)
13859     Board board;
13860      int *blackPlaysFirst;
13861      char *fen;
13862 {
13863     int i, j;
13864     char *p;
13865     int emptycount;
13866     ChessSquare piece;
13867
13868     p = fen;
13869
13870     /* [HGM] by default clear Crazyhouse holdings, if present */
13871     if(gameInfo.holdingsWidth) {
13872        for(i=0; i<BOARD_HEIGHT; i++) {
13873            board[i][0]             = EmptySquare; /* black holdings */
13874            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13875            board[i][1]             = (ChessSquare) 0; /* black counts */
13876            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13877        }
13878     }
13879
13880     /* Piece placement data */
13881     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13882         j = 0;
13883         for (;;) {
13884             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13885                 if (*p == '/') p++;
13886                 emptycount = gameInfo.boardWidth - j;
13887                 while (emptycount--)
13888                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13889                 break;
13890 #if(BOARD_SIZE >= 10)
13891             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13892                 p++; emptycount=10;
13893                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13894                 while (emptycount--)
13895                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13896 #endif
13897             } else if (isdigit(*p)) {
13898                 emptycount = *p++ - '0';
13899                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13900                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13901                 while (emptycount--)
13902                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13903             } else if (*p == '+' || isalpha(*p)) {
13904                 if (j >= gameInfo.boardWidth) return FALSE;
13905                 if(*p=='+') {
13906                     piece = CharToPiece(*++p);
13907                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13908                     piece = (ChessSquare) (PROMOTED piece ); p++;
13909                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13910                 } else piece = CharToPiece(*p++);
13911
13912                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13913                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13914                     piece = (ChessSquare) (PROMOTED piece);
13915                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13916                     p++;
13917                 }
13918                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13919             } else {
13920                 return FALSE;
13921             }
13922         }
13923     }
13924     while (*p == '/' || *p == ' ') p++;
13925
13926     /* [HGM] look for Crazyhouse holdings here */
13927     while(*p==' ') p++;
13928     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13929         if(*p == '[') p++;
13930         if(*p == '-' ) *p++; /* empty holdings */ else {
13931             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13932             /* if we would allow FEN reading to set board size, we would   */
13933             /* have to add holdings and shift the board read so far here   */
13934             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13935                 *p++;
13936                 if((int) piece >= (int) BlackPawn ) {
13937                     i = (int)piece - (int)BlackPawn;
13938                     i = PieceToNumber((ChessSquare)i);
13939                     if( i >= gameInfo.holdingsSize ) return FALSE;
13940                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13941                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13942                 } else {
13943                     i = (int)piece - (int)WhitePawn;
13944                     i = PieceToNumber((ChessSquare)i);
13945                     if( i >= gameInfo.holdingsSize ) return FALSE;
13946                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13947                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13948                 }
13949             }
13950         }
13951         if(*p == ']') *p++;
13952     }
13953
13954     while(*p == ' ') p++;
13955
13956     /* Active color */
13957     switch (*p++) {
13958       case 'w':
13959         *blackPlaysFirst = FALSE;
13960         break;
13961       case 'b':
13962         *blackPlaysFirst = TRUE;
13963         break;
13964       default:
13965         return FALSE;
13966     }
13967
13968     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13969     /* return the extra info in global variiables             */
13970
13971     /* set defaults in case FEN is incomplete */
13972     FENepStatus = EP_UNKNOWN;
13973     for(i=0; i<nrCastlingRights; i++ ) {
13974         FENcastlingRights[i] =
13975             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13976     }   /* assume possible unless obviously impossible */
13977     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13978     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13979     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13980     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13981     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13982     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13983     FENrulePlies = 0;
13984
13985     while(*p==' ') p++;
13986     if(nrCastlingRights) {
13987       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13988           /* castling indicator present, so default becomes no castlings */
13989           for(i=0; i<nrCastlingRights; i++ ) {
13990                  FENcastlingRights[i] = -1;
13991           }
13992       }
13993       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13994              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13995              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13996              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13997         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13998
13999         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14000             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14001             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14002         }
14003         switch(c) {
14004           case'K':
14005               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14006               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
14007               FENcastlingRights[2] = whiteKingFile;
14008               break;
14009           case'Q':
14010               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14011               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14012               FENcastlingRights[2] = whiteKingFile;
14013               break;
14014           case'k':
14015               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14016               FENcastlingRights[3] = i != blackKingFile ? i : -1;
14017               FENcastlingRights[5] = blackKingFile;
14018               break;
14019           case'q':
14020               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14021               FENcastlingRights[4] = i != blackKingFile ? i : -1;
14022               FENcastlingRights[5] = blackKingFile;
14023           case '-':
14024               break;
14025           default: /* FRC castlings */
14026               if(c >= 'a') { /* black rights */
14027                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14028                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14029                   if(i == BOARD_RGHT) break;
14030                   FENcastlingRights[5] = i;
14031                   c -= AAA;
14032                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14033                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14034                   if(c > i)
14035                       FENcastlingRights[3] = c;
14036                   else
14037                       FENcastlingRights[4] = c;
14038               } else { /* white rights */
14039                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14040                     if(board[0][i] == WhiteKing) break;
14041                   if(i == BOARD_RGHT) break;
14042                   FENcastlingRights[2] = i;
14043                   c -= AAA - 'a' + 'A';
14044                   if(board[0][c] >= WhiteKing) break;
14045                   if(c > i)
14046                       FENcastlingRights[0] = c;
14047                   else
14048                       FENcastlingRights[1] = c;
14049               }
14050         }
14051       }
14052     if (appData.debugMode) {
14053         fprintf(debugFP, "FEN castling rights:");
14054         for(i=0; i<nrCastlingRights; i++)
14055         fprintf(debugFP, " %d", FENcastlingRights[i]);
14056         fprintf(debugFP, "\n");
14057     }
14058
14059       while(*p==' ') p++;
14060     }
14061
14062     /* read e.p. field in games that know e.p. capture */
14063     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14064        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14065       if(*p=='-') {
14066         p++; FENepStatus = EP_NONE;
14067       } else {
14068          char c = *p++ - AAA;
14069
14070          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14071          if(*p >= '0' && *p <='9') *p++;
14072          FENepStatus = c;
14073       }
14074     }
14075
14076
14077     if(sscanf(p, "%d", &i) == 1) {
14078         FENrulePlies = i; /* 50-move ply counter */
14079         /* (The move number is still ignored)    */
14080     }
14081
14082     return TRUE;
14083 }
14084
14085 void
14086 EditPositionPasteFEN(char *fen)
14087 {
14088   if (fen != NULL) {
14089     Board initial_position;
14090
14091     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14092       DisplayError(_("Bad FEN position in clipboard"), 0);
14093       return ;
14094     } else {
14095       int savedBlackPlaysFirst = blackPlaysFirst;
14096       EditPositionEvent();
14097       blackPlaysFirst = savedBlackPlaysFirst;
14098       CopyBoard(boards[0], initial_position);
14099           /* [HGM] copy FEN attributes as well */
14100           {   int i;
14101               initialRulePlies = FENrulePlies;
14102               epStatus[0] = FENepStatus;
14103               for( i=0; i<nrCastlingRights; i++ )
14104                   castlingRights[0][i] = FENcastlingRights[i];
14105           }
14106       EditPositionDone();
14107       DisplayBothClocks();
14108       DrawPosition(FALSE, boards[currentMove]);
14109     }
14110   }
14111 }
14112
14113 static char cseq[12] = "\\   ";
14114
14115 Boolean set_cont_sequence(char *new_seq)
14116 {
14117     int len;
14118     Boolean ret;
14119
14120     // handle bad attempts to set the sequence
14121         if (!new_seq)
14122                 return 0; // acceptable error - no debug
14123
14124     len = strlen(new_seq);
14125     ret = (len > 0) && (len < sizeof(cseq));
14126     if (ret)
14127         strcpy(cseq, new_seq);
14128     else if (appData.debugMode)
14129         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14130     return ret;
14131 }
14132
14133 /*
14134     reformat a source message so words don't cross the width boundary.  internal
14135     newlines are not removed.  returns the wrapped size (no null character unless
14136     included in source message).  If dest is NULL, only calculate the size required
14137     for the dest buffer.  lp argument indicats line position upon entry, and it's
14138     passed back upon exit.
14139 */
14140 int wrap(char *dest, char *src, int count, int width, int *lp)
14141 {
14142     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14143
14144     cseq_len = strlen(cseq);
14145     old_line = line = *lp;
14146     ansi = len = clen = 0;
14147
14148     for (i=0; i < count; i++)
14149     {
14150         if (src[i] == '\033')
14151             ansi = 1;
14152
14153         // if we hit the width, back up
14154         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14155         {
14156             // store i & len in case the word is too long
14157             old_i = i, old_len = len;
14158
14159             // find the end of the last word
14160             while (i && src[i] != ' ' && src[i] != '\n')
14161             {
14162                 i--;
14163                 len--;
14164             }
14165
14166             // word too long?  restore i & len before splitting it
14167             if ((old_i-i+clen) >= width)
14168             {
14169                 i = old_i;
14170                 len = old_len;
14171             }
14172
14173             // extra space?
14174             if (i && src[i-1] == ' ')
14175                 len--;
14176
14177             if (src[i] != ' ' && src[i] != '\n')
14178             {
14179                 i--;
14180                 if (len)
14181                     len--;
14182             }
14183
14184             // now append the newline and continuation sequence
14185             if (dest)
14186                 dest[len] = '\n';
14187             len++;
14188             if (dest)
14189                 strncpy(dest+len, cseq, cseq_len);
14190             len += cseq_len;
14191             line = cseq_len;
14192             clen = cseq_len;
14193             continue;
14194         }
14195
14196         if (dest)
14197             dest[len] = src[i];
14198         len++;
14199         if (!ansi)
14200             line++;
14201         if (src[i] == '\n')
14202             line = 0;
14203         if (src[i] == 'm')
14204             ansi = 0;
14205     }
14206     if (dest && appData.debugMode)
14207     {
14208         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14209             count, width, line, len, *lp);
14210         show_bytes(debugFP, src, count);
14211         fprintf(debugFP, "\ndest: ");
14212         show_bytes(debugFP, dest, len);
14213         fprintf(debugFP, "\n");
14214     }
14215     *lp = dest ? line : old_line;
14216
14217     return len;
14218 }