new developer release
[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));
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((Boolean fakeRights));
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 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
239 int endPV = -1;
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
243 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
253
254 /* States for ics_getting_history */
255 #define H_FALSE 0
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
261
262 /* whosays values for GameEnds */
263 #define GE_ICS 0
264 #define GE_ENGINE 1
265 #define GE_PLAYER 2
266 #define GE_FILE 3
267 #define GE_XBOARD 4
268 #define GE_ENGINE1 5
269 #define GE_ENGINE2 6
270
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
273
274 /* Different types of move when calling RegisterMove */
275 #define CMAIL_MOVE   0
276 #define CMAIL_RESIGN 1
277 #define CMAIL_DRAW   2
278 #define CMAIL_ACCEPT 3
279
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
284
285 /* Telnet protocol constants */
286 #define TN_WILL 0373
287 #define TN_WONT 0374
288 #define TN_DO   0375
289 #define TN_DONT 0376
290 #define TN_IAC  0377
291 #define TN_ECHO 0001
292 #define TN_SGA  0003
293 #define TN_PORT 23
294
295 /* [AS] */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 {
298     assert( dst != NULL );
299     assert( src != NULL );
300     assert( count > 0 );
301
302     strncpy( dst, src, count );
303     dst[ count-1 ] = '\0';
304     return dst;
305 }
306
307 /* Some compiler can't cast u64 to double
308  * This function do the job for us:
309
310  * We use the highest bit for cast, this only
311  * works if the highest bit is not
312  * in use (This should not happen)
313  *
314  * We used this for all compiler
315  */
316 double
317 u64ToDouble(u64 value)
318 {
319   double r;
320   u64 tmp = value & u64Const(0x7fffffffffffffff);
321   r = (double)(s64)tmp;
322   if (value & u64Const(0x8000000000000000))
323        r +=  9.2233720368547758080e18; /* 2^63 */
324  return r;
325 }
326
327 /* Fake up flags for now, as we aren't keeping track of castling
328    availability yet. [HGM] Change of logic: the flag now only
329    indicates the type of castlings allowed by the rule of the game.
330    The actual rights themselves are maintained in the array
331    castlingRights, as part of the game history, and are not probed
332    by this function.
333  */
334 int
335 PosFlags(index)
336 {
337   int flags = F_ALL_CASTLE_OK;
338   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339   switch (gameInfo.variant) {
340   case VariantSuicide:
341     flags &= ~F_ALL_CASTLE_OK;
342   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343     flags |= F_IGNORE_CHECK;
344   case VariantLosers:
345     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346     break;
347   case VariantAtomic:
348     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349     break;
350   case VariantKriegspiel:
351     flags |= F_KRIEGSPIEL_CAPTURE;
352     break;
353   case VariantCapaRandom:
354   case VariantFischeRandom:
355     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356   case VariantNoCastle:
357   case VariantShatranj:
358   case VariantCourier:
359   case VariantMakruk:
360     flags &= ~F_ALL_CASTLE_OK;
361     break;
362   default:
363     break;
364   }
365   return flags;
366 }
367
368 FILE *gameFileFP, *debugFP;
369
370 /*
371     [AS] Note: sometimes, the sscanf() function is used to parse the input
372     into a fixed-size buffer. Because of this, we must be prepared to
373     receive strings as long as the size of the input buffer, which is currently
374     set to 4K for Windows and 8K for the rest.
375     So, we must either allocate sufficiently large buffers here, or
376     reduce the size of the input buffer in the input reading part.
377 */
378
379 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
380 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
381 char thinkOutput1[MSG_SIZ*10];
382
383 ChessProgramState first, second;
384
385 /* premove variables */
386 int premoveToX = 0;
387 int premoveToY = 0;
388 int premoveFromX = 0;
389 int premoveFromY = 0;
390 int premovePromoChar = 0;
391 int gotPremove = 0;
392 Boolean alarmSounded;
393 /* end premove variables */
394
395 char *ics_prefix = "$";
396 int ics_type = ICS_GENERIC;
397
398 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
399 int pauseExamForwardMostMove = 0;
400 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
401 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
402 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
403 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
404 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
405 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
406 int whiteFlag = FALSE, blackFlag = FALSE;
407 int userOfferedDraw = FALSE;
408 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
409 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
410 int cmailMoveType[CMAIL_MAX_GAMES];
411 long ics_clock_paused = 0;
412 ProcRef icsPR = NoProc, cmailPR = NoProc;
413 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
414 GameMode gameMode = BeginningOfGame;
415 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
416 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
417 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
418 int hiddenThinkOutputState = 0; /* [AS] */
419 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
420 int adjudicateLossPlies = 6;
421 char white_holding[64], black_holding[64];
422 TimeMark lastNodeCountTime;
423 long lastNodeCount=0;
424 int have_sent_ICS_logon = 0;
425 int movesPerSession;
426 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
427 long timeControl_2; /* [AS] Allow separate time controls */
428 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
429 long timeRemaining[2][MAX_MOVES];
430 int matchGame = 0;
431 TimeMark programStartTime;
432 char ics_handle[MSG_SIZ];
433 int have_set_title = 0;
434
435 /* animateTraining preserves the state of appData.animate
436  * when Training mode is activated. This allows the
437  * response to be animated when appData.animate == TRUE and
438  * appData.animateDragging == TRUE.
439  */
440 Boolean animateTraining;
441
442 GameInfo gameInfo;
443
444 AppData appData;
445
446 Board boards[MAX_MOVES];
447 /* [HGM] Following 7 needed for accurate legality tests: */
448 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
449 signed char  initialRights[BOARD_FILES];
450 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
451 int   initialRulePlies, FENrulePlies;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0;
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 // [HGM] vari: next 12 to save and restore variations
458 #define MAX_VARIATIONS 10
459 int framePtr = MAX_MOVES-1; // points to free stack entry
460 int storedGames = 0;
461 int savedFirst[MAX_VARIATIONS];
462 int savedLast[MAX_VARIATIONS];
463 int savedFramePtr[MAX_VARIATIONS];
464 char *savedDetails[MAX_VARIATIONS];
465 ChessMove savedResult[MAX_VARIATIONS];
466
467 void PushTail P((int firstMove, int lastMove));
468 Boolean PopTail P((Boolean annotate));
469 void CleanupTail P((void));
470
471 ChessSquare  FIDEArray[2][BOARD_FILES] = {
472     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475         BlackKing, BlackBishop, BlackKnight, BlackRook }
476 };
477
478 ChessSquare twoKingsArray[2][BOARD_FILES] = {
479     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482         BlackKing, BlackKing, BlackKnight, BlackRook }
483 };
484
485 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
486     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488     { BlackRook, BlackMan, BlackBishop, BlackQueen,
489         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
490 };
491
492 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
493     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
494         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
496         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
497 };
498
499 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
504 };
505
506 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
507     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
508         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackMan, BlackFerz,
510         BlackKing, BlackMan, BlackKnight, BlackRook }
511 };
512
513
514 #if (BOARD_FILES>=10)
515 ChessSquare ShogiArray[2][BOARD_FILES] = {
516     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
517         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
518     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
519         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
520 };
521
522 ChessSquare XiangqiArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
524         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
526         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
527 };
528
529 ChessSquare CapablancaArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
533         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
534 };
535
536 ChessSquare GreatArray[2][BOARD_FILES] = {
537     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
538         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
539     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
540         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
541 };
542
543 ChessSquare JanusArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
545         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
546     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
547         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
548 };
549
550 #ifdef GOTHIC
551 ChessSquare GothicArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
553         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
555         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !GOTHIC
558 #define GothicArray CapablancaArray
559 #endif // !GOTHIC
560
561 #ifdef FALCON
562 ChessSquare FalconArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
564         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
566         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
567 };
568 #else // !FALCON
569 #define FalconArray CapablancaArray
570 #endif // !FALCON
571
572 #else // !(BOARD_FILES>=10)
573 #define XiangqiPosition FIDEArray
574 #define CapablancaArray FIDEArray
575 #define GothicArray FIDEArray
576 #define GreatArray FIDEArray
577 #endif // !(BOARD_FILES>=10)
578
579 #if (BOARD_FILES>=12)
580 ChessSquare CourierArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
582         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
584         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
585 };
586 #else // !(BOARD_FILES>=12)
587 #define CourierArray CapablancaArray
588 #endif // !(BOARD_FILES>=12)
589
590
591 Board initialPosition;
592
593
594 /* Convert str to a rating. Checks for special cases of "----",
595
596    "++++", etc. Also strips ()'s */
597 int
598 string_to_rating(str)
599   char *str;
600 {
601   while(*str && !isdigit(*str)) ++str;
602   if (!*str)
603     return 0;   /* One of the special "no rating" cases */
604   else
605     return atoi(str);
606 }
607
608 void
609 ClearProgramStats()
610 {
611     /* Init programStats */
612     programStats.movelist[0] = 0;
613     programStats.depth = 0;
614     programStats.nr_moves = 0;
615     programStats.moves_left = 0;
616     programStats.nodes = 0;
617     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
618     programStats.score = 0;
619     programStats.got_only_move = 0;
620     programStats.got_fail = 0;
621     programStats.line_is_book = 0;
622 }
623
624 void
625 InitBackEnd1()
626 {
627     int matched, min, sec;
628
629     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
630
631     GetTimeMark(&programStartTime);
632     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
633
634     ClearProgramStats();
635     programStats.ok_to_send = 1;
636     programStats.seen_stat = 0;
637
638     /*
639      * Initialize game list
640      */
641     ListNew(&gameList);
642
643
644     /*
645      * Internet chess server status
646      */
647     if (appData.icsActive) {
648         appData.matchMode = FALSE;
649         appData.matchGames = 0;
650 #if ZIPPY
651         appData.noChessProgram = !appData.zippyPlay;
652 #else
653         appData.zippyPlay = FALSE;
654         appData.zippyTalk = FALSE;
655         appData.noChessProgram = TRUE;
656 #endif
657         if (*appData.icsHelper != NULLCHAR) {
658             appData.useTelnet = TRUE;
659             appData.telnetProgram = appData.icsHelper;
660         }
661     } else {
662         appData.zippyTalk = appData.zippyPlay = FALSE;
663     }
664
665     /* [AS] Initialize pv info list [HGM] and game state */
666     {
667         int i, j;
668
669         for( i=0; i<=framePtr; i++ ) {
670             pvInfoList[i].depth = -1;
671             boards[i][EP_STATUS] = EP_NONE;
672             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
673         }
674     }
675
676     /*
677      * Parse timeControl resource
678      */
679     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
680                           appData.movesPerSession)) {
681         char buf[MSG_SIZ];
682         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
683         DisplayFatalError(buf, 0, 2);
684     }
685
686     /*
687      * Parse searchTime resource
688      */
689     if (*appData.searchTime != NULLCHAR) {
690         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
691         if (matched == 1) {
692             searchTime = min * 60;
693         } else if (matched == 2) {
694             searchTime = min * 60 + sec;
695         } else {
696             char buf[MSG_SIZ];
697             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
698             DisplayFatalError(buf, 0, 2);
699         }
700     }
701
702     /* [AS] Adjudication threshold */
703     adjudicateLossThreshold = appData.adjudicateLossThreshold;
704
705     first.which = "first";
706     second.which = "second";
707     first.maybeThinking = second.maybeThinking = FALSE;
708     first.pr = second.pr = NoProc;
709     first.isr = second.isr = NULL;
710     first.sendTime = second.sendTime = 2;
711     first.sendDrawOffers = 1;
712     if (appData.firstPlaysBlack) {
713         first.twoMachinesColor = "black\n";
714         second.twoMachinesColor = "white\n";
715     } else {
716         first.twoMachinesColor = "white\n";
717         second.twoMachinesColor = "black\n";
718     }
719     first.program = appData.firstChessProgram;
720     second.program = appData.secondChessProgram;
721     first.host = appData.firstHost;
722     second.host = appData.secondHost;
723     first.dir = appData.firstDirectory;
724     second.dir = appData.secondDirectory;
725     first.other = &second;
726     second.other = &first;
727     first.initString = appData.initString;
728     second.initString = appData.secondInitString;
729     first.computerString = appData.firstComputerString;
730     second.computerString = appData.secondComputerString;
731     first.useSigint = second.useSigint = TRUE;
732     first.useSigterm = second.useSigterm = TRUE;
733     first.reuse = appData.reuseFirst;
734     second.reuse = appData.reuseSecond;
735     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
736     second.nps = appData.secondNPS;
737     first.useSetboard = second.useSetboard = FALSE;
738     first.useSAN = second.useSAN = FALSE;
739     first.usePing = second.usePing = FALSE;
740     first.lastPing = second.lastPing = 0;
741     first.lastPong = second.lastPong = 0;
742     first.usePlayother = second.usePlayother = FALSE;
743     first.useColors = second.useColors = TRUE;
744     first.useUsermove = second.useUsermove = FALSE;
745     first.sendICS = second.sendICS = FALSE;
746     first.sendName = second.sendName = appData.icsActive;
747     first.sdKludge = second.sdKludge = FALSE;
748     first.stKludge = second.stKludge = FALSE;
749     TidyProgramName(first.program, first.host, first.tidy);
750     TidyProgramName(second.program, second.host, second.tidy);
751     first.matchWins = second.matchWins = 0;
752     strcpy(first.variants, appData.variant);
753     strcpy(second.variants, appData.variant);
754     first.analysisSupport = second.analysisSupport = 2; /* detect */
755     first.analyzing = second.analyzing = FALSE;
756     first.initDone = second.initDone = FALSE;
757
758     /* New features added by Tord: */
759     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
760     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
761     /* End of new features added by Tord. */
762     first.fenOverride  = appData.fenOverride1;
763     second.fenOverride = appData.fenOverride2;
764
765     /* [HGM] time odds: set factor for each machine */
766     first.timeOdds  = appData.firstTimeOdds;
767     second.timeOdds = appData.secondTimeOdds;
768     { float norm = 1;
769         if(appData.timeOddsMode) {
770             norm = first.timeOdds;
771             if(norm > second.timeOdds) norm = second.timeOdds;
772         }
773         first.timeOdds /= norm;
774         second.timeOdds /= norm;
775     }
776
777     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
778     first.accumulateTC = appData.firstAccumulateTC;
779     second.accumulateTC = appData.secondAccumulateTC;
780     first.maxNrOfSessions = second.maxNrOfSessions = 1;
781
782     /* [HGM] debug */
783     first.debug = second.debug = FALSE;
784     first.supportsNPS = second.supportsNPS = UNKNOWN;
785
786     /* [HGM] options */
787     first.optionSettings  = appData.firstOptions;
788     second.optionSettings = appData.secondOptions;
789
790     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
791     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
792     first.isUCI = appData.firstIsUCI; /* [AS] */
793     second.isUCI = appData.secondIsUCI; /* [AS] */
794     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
795     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
796
797     if (appData.firstProtocolVersion > PROTOVER ||
798         appData.firstProtocolVersion < 1) {
799       char buf[MSG_SIZ];
800       sprintf(buf, _("protocol version %d not supported"),
801               appData.firstProtocolVersion);
802       DisplayFatalError(buf, 0, 2);
803     } else {
804       first.protocolVersion = appData.firstProtocolVersion;
805     }
806
807     if (appData.secondProtocolVersion > PROTOVER ||
808         appData.secondProtocolVersion < 1) {
809       char buf[MSG_SIZ];
810       sprintf(buf, _("protocol version %d not supported"),
811               appData.secondProtocolVersion);
812       DisplayFatalError(buf, 0, 2);
813     } else {
814       second.protocolVersion = appData.secondProtocolVersion;
815     }
816
817     if (appData.icsActive) {
818         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
819 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
820     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
821         appData.clockMode = FALSE;
822         first.sendTime = second.sendTime = 0;
823     }
824
825 #if ZIPPY
826     /* Override some settings from environment variables, for backward
827        compatibility.  Unfortunately it's not feasible to have the env
828        vars just set defaults, at least in xboard.  Ugh.
829     */
830     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
831       ZippyInit();
832     }
833 #endif
834
835     if (appData.noChessProgram) {
836         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
837         sprintf(programVersion, "%s", PACKAGE_STRING);
838     } else {
839       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
840       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
841       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
842     }
843
844     if (!appData.icsActive) {
845       char buf[MSG_SIZ];
846       /* Check for variants that are supported only in ICS mode,
847          or not at all.  Some that are accepted here nevertheless
848          have bugs; see comments below.
849       */
850       VariantClass variant = StringToVariant(appData.variant);
851       switch (variant) {
852       case VariantBughouse:     /* need four players and two boards */
853       case VariantKriegspiel:   /* need to hide pieces and move details */
854       /* case VariantFischeRandom: (Fabien: moved below) */
855         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
856         DisplayFatalError(buf, 0, 2);
857         return;
858
859       case VariantUnknown:
860       case VariantLoadable:
861       case Variant29:
862       case Variant30:
863       case Variant31:
864       case Variant32:
865       case Variant33:
866       case Variant34:
867       case Variant35:
868       case Variant36:
869       default:
870         sprintf(buf, _("Unknown variant name %s"), appData.variant);
871         DisplayFatalError(buf, 0, 2);
872         return;
873
874       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
875       case VariantFairy:      /* [HGM] TestLegality definitely off! */
876       case VariantGothic:     /* [HGM] should work */
877       case VariantCapablanca: /* [HGM] should work */
878       case VariantCourier:    /* [HGM] initial forced moves not implemented */
879       case VariantShogi:      /* [HGM] drops not tested for legality */
880       case VariantKnightmate: /* [HGM] should work */
881       case VariantCylinder:   /* [HGM] untested */
882       case VariantFalcon:     /* [HGM] untested */
883       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
884                                  offboard interposition not understood */
885       case VariantNormal:     /* definitely works! */
886       case VariantWildCastle: /* pieces not automatically shuffled */
887       case VariantNoCastle:   /* pieces not automatically shuffled */
888       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
889       case VariantLosers:     /* should work except for win condition,
890                                  and doesn't know captures are mandatory */
891       case VariantSuicide:    /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantGiveaway:   /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantTwoKings:   /* should work */
896       case VariantAtomic:     /* should work except for win condition */
897       case Variant3Check:     /* should work except for win condition */
898       case VariantShatranj:   /* should work except for all win conditions */
899       case VariantMakruk:     /* should work except for daw countdown */
900       case VariantBerolina:   /* might work if TestLegality is off */
901       case VariantCapaRandom: /* should work */
902       case VariantJanus:      /* should work */
903       case VariantSuper:      /* experimental */
904       case VariantGreat:      /* experimental, requires legality testing to be off */
905         break;
906       }
907     }
908
909     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
910     InitEngineUCI( installDir, &second );
911 }
912
913 int NextIntegerFromString( char ** str, long * value )
914 {
915     int result = -1;
916     char * s = *str;
917
918     while( *s == ' ' || *s == '\t' ) {
919         s++;
920     }
921
922     *value = 0;
923
924     if( *s >= '0' && *s <= '9' ) {
925         while( *s >= '0' && *s <= '9' ) {
926             *value = *value * 10 + (*s - '0');
927             s++;
928         }
929
930         result = 0;
931     }
932
933     *str = s;
934
935     return result;
936 }
937
938 int NextTimeControlFromString( char ** str, long * value )
939 {
940     long temp;
941     int result = NextIntegerFromString( str, &temp );
942
943     if( result == 0 ) {
944         *value = temp * 60; /* Minutes */
945         if( **str == ':' ) {
946             (*str)++;
947             result = NextIntegerFromString( str, &temp );
948             *value += temp; /* Seconds */
949         }
950     }
951
952     return result;
953 }
954
955 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
956 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
957     int result = -1; long temp, temp2;
958
959     if(**str != '+') return -1; // old params remain in force!
960     (*str)++;
961     if( NextTimeControlFromString( str, &temp ) ) return -1;
962
963     if(**str != '/') {
964         /* time only: incremental or sudden-death time control */
965         if(**str == '+') { /* increment follows; read it */
966             (*str)++;
967             if(result = NextIntegerFromString( str, &temp2)) return -1;
968             *inc = temp2 * 1000;
969         } else *inc = 0;
970         *moves = 0; *tc = temp * 1000;
971         return 0;
972     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
973
974     (*str)++; /* classical time control */
975     result = NextTimeControlFromString( str, &temp2);
976     if(result == 0) {
977         *moves = temp/60;
978         *tc    = temp2 * 1000;
979         *inc   = 0;
980     }
981     return result;
982 }
983
984 int GetTimeQuota(int movenr)
985 {   /* [HGM] get time to add from the multi-session time-control string */
986     int moves=1; /* kludge to force reading of first session */
987     long time, increment;
988     char *s = fullTimeControlString;
989
990     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
991     do {
992         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
993         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
994         if(movenr == -1) return time;    /* last move before new session     */
995         if(!moves) return increment;     /* current session is incremental   */
996         if(movenr >= 0) movenr -= moves; /* we already finished this session */
997     } while(movenr >= -1);               /* try again for next session       */
998
999     return 0; // no new time quota on this move
1000 }
1001
1002 int
1003 ParseTimeControl(tc, ti, mps)
1004      char *tc;
1005      int ti;
1006      int mps;
1007 {
1008   long tc1;
1009   long tc2;
1010   char buf[MSG_SIZ];
1011   
1012   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1013   if(ti > 0) {
1014     if(mps)
1015       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1016     else sprintf(buf, "+%s+%d", tc, ti);
1017   } else {
1018     if(mps)
1019              sprintf(buf, "+%d/%s", mps, tc);
1020     else sprintf(buf, "+%s", tc);
1021   }
1022   fullTimeControlString = StrSave(buf);
1023   
1024   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1025     return FALSE;
1026   }
1027   
1028   if( *tc == '/' ) {
1029     /* Parse second time control */
1030     tc++;
1031     
1032     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1033       return FALSE;
1034     }
1035     
1036     if( tc2 == 0 ) {
1037       return FALSE;
1038     }
1039     
1040     timeControl_2 = tc2 * 1000;
1041   }
1042   else {
1043     timeControl_2 = 0;
1044   }
1045   
1046   if( tc1 == 0 ) {
1047     return FALSE;
1048   }
1049   
1050   timeControl = tc1 * 1000;
1051   
1052   if (ti >= 0) {
1053     timeIncrement = ti * 1000;  /* convert to ms */
1054     movesPerSession = 0;
1055   } else {
1056     timeIncrement = 0;
1057     movesPerSession = mps;
1058   }
1059   return TRUE;
1060 }
1061
1062 void
1063 InitBackEnd2()
1064 {
1065   if (appData.debugMode) {
1066     fprintf(debugFP, "%s\n", programVersion);
1067   }
1068
1069   set_cont_sequence(appData.wrapContSeq);
1070   if (appData.matchGames > 0) {
1071     appData.matchMode = TRUE;
1072   } else if (appData.matchMode) {
1073     appData.matchGames = 1;
1074   }
1075   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1076     appData.matchGames = appData.sameColorGames;
1077   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1078     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1079     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1080   }
1081   Reset(TRUE, FALSE);
1082   if (appData.noChessProgram || first.protocolVersion == 1) {
1083     InitBackEnd3();
1084     } else {
1085     /* kludge: allow timeout for initial "feature" commands */
1086     FreezeUI();
1087     DisplayMessage("", _("Starting chess program"));
1088     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1089   }
1090 }
1091
1092 void
1093 InitBackEnd3 P((void))
1094 {
1095   GameMode initialMode;
1096   char buf[MSG_SIZ];
1097   int err;
1098   
1099   InitChessProgram(&first, startedFromSetupPosition);
1100   
1101   
1102   if (appData.icsActive) 
1103     {
1104       err = establish();
1105       if (err != 0) 
1106         {
1107           if (*appData.icsCommPort != NULLCHAR) 
1108             {
1109               sprintf(buf, _("Could not open comm port %s"),
1110                       appData.icsCommPort);
1111             }
1112           else 
1113             {
1114               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1115                        appData.icsHost, appData.icsPort);
1116             }
1117           DisplayFatalError(buf, err, 1);
1118           return;
1119         }
1120       SetICSMode();
1121       telnetISR =
1122         AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1123       fromUserISR =
1124         AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1125     }
1126   else if (appData.noChessProgram) 
1127     {
1128       SetNCPMode();
1129     } 
1130   else 
1131     {
1132       SetGNUMode();
1133     }
1134   
1135   if (*appData.cmailGameName != NULLCHAR) 
1136     {
1137       SetCmailMode();
1138       OpenLoopback(&cmailPR);
1139       cmailISR =
1140         AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1141     }
1142   
1143   ThawUI();
1144   DisplayMessage("", "");
1145   if (StrCaseCmp(appData.initialMode, "") == 0) 
1146     {
1147       initialMode = BeginningOfGame;
1148     } 
1149   else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) 
1150     {
1151       initialMode = TwoMachinesPlay;
1152     } 
1153   else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) 
1154     {
1155       initialMode = AnalyzeFile;
1156     } 
1157   else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) 
1158     {
1159       initialMode = AnalyzeMode;
1160     } 
1161   else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) 
1162     {
1163       initialMode = MachinePlaysWhite;
1164     } 
1165   else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) 
1166     {
1167       initialMode = MachinePlaysBlack;
1168     } 
1169   else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) 
1170     {
1171       initialMode = EditGame;
1172     } 
1173   else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) 
1174     {
1175       initialMode = EditPosition;
1176     } 
1177   else if (StrCaseCmp(appData.initialMode, "Training") == 0) 
1178     {
1179       initialMode = Training;
1180     } 
1181   else 
1182     {
1183       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1184       DisplayFatalError(buf, 0, 2);
1185       return;
1186     }
1187   
1188   if (appData.matchMode) 
1189     {
1190       /* Set up machine vs. machine match */
1191       if (appData.noChessProgram) 
1192         {
1193           DisplayFatalError(_("Can't have a match with no chess programs"),
1194                             0, 2);
1195           return;
1196         }
1197       matchMode = TRUE;
1198       matchGame = 1;
1199       if (*appData.loadGameFile != NULLCHAR) 
1200         {
1201           int index = appData.loadGameIndex; // [HGM] autoinc
1202           if(index<0) lastIndex = index = 1;
1203           if (!LoadGameFromFile(appData.loadGameFile,
1204                                 index,
1205                                 appData.loadGameFile, FALSE)) 
1206             {
1207               DisplayFatalError(_("Bad game file"), 0, 1);
1208               return;
1209             }
1210         } 
1211       else if (*appData.loadPositionFile != NULLCHAR) 
1212         {
1213           int index = appData.loadPositionIndex; // [HGM] autoinc
1214           if(index<0) lastIndex = index = 1;
1215           if (!LoadPositionFromFile(appData.loadPositionFile,
1216                                     index,
1217                                     appData.loadPositionFile)) 
1218             {
1219               DisplayFatalError(_("Bad position file"), 0, 1);
1220               return;
1221             }
1222         }
1223       TwoMachinesEvent();
1224     } 
1225   else if (*appData.cmailGameName != NULLCHAR) 
1226     {
1227       /* Set up cmail mode */
1228       ReloadCmailMsgEvent(TRUE);
1229     } 
1230   else 
1231     {
1232       /* Set up other modes */
1233       if (initialMode == AnalyzeFile) 
1234         {
1235           if (*appData.loadGameFile == NULLCHAR) 
1236             {
1237               DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1238               return;
1239             }
1240         }
1241       if (*appData.loadGameFile != NULLCHAR) 
1242         {
1243           (void) LoadGameFromFile(appData.loadGameFile,
1244                                   appData.loadGameIndex,
1245                                   appData.loadGameFile, TRUE);
1246         } 
1247       else if (*appData.loadPositionFile != NULLCHAR) 
1248         {
1249           (void) LoadPositionFromFile(appData.loadPositionFile,
1250                                       appData.loadPositionIndex,
1251                                       appData.loadPositionFile);
1252           /* [HGM] try to make self-starting even after FEN load */
1253           /* to allow automatic setup of fairy variants with wtm */
1254           if(initialMode == BeginningOfGame && !blackPlaysFirst) 
1255             {
1256               gameMode = BeginningOfGame;
1257               setboardSpoiledMachineBlack = 1;
1258             }
1259           /* [HGM] loadPos: make that every new game uses the setup */
1260           /* from file as long as we do not switch variant          */
1261           if(!blackPlaysFirst) 
1262             {
1263               startedFromPositionFile = TRUE;
1264               CopyBoard(filePosition, boards[0]);
1265             }
1266         }
1267       if (initialMode == AnalyzeMode) 
1268         {
1269           if (appData.noChessProgram) 
1270             {
1271               DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1272               return;
1273             }
1274           if (appData.icsActive) 
1275             {
1276               DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1277               return;
1278             }
1279           AnalyzeModeEvent();
1280         } 
1281       else if (initialMode == AnalyzeFile) 
1282         {
1283           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1284           ShowThinkingEvent();
1285           AnalyzeFileEvent();
1286           AnalysisPeriodicEvent(1);
1287         } 
1288       else if (initialMode == MachinePlaysWhite) 
1289         {
1290           if (appData.noChessProgram) 
1291             {
1292               DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1293                                 0, 2);
1294               return;
1295             }
1296           if (appData.icsActive) 
1297             {
1298               DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1299                                 0, 2);
1300               return;
1301             }
1302           MachineWhiteEvent();
1303         } 
1304       else if (initialMode == MachinePlaysBlack) 
1305         {
1306           if (appData.noChessProgram) 
1307             {
1308               DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1309                                 0, 2);
1310               return;
1311             }
1312           if (appData.icsActive) 
1313             {
1314               DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1315                                 0, 2);
1316               return;
1317             }
1318           MachineBlackEvent();
1319         } 
1320       else if (initialMode == TwoMachinesPlay) 
1321         {
1322           if (appData.noChessProgram) 
1323             {
1324               DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1325                                 0, 2);
1326               return;
1327             }
1328           if (appData.icsActive) 
1329             {
1330               DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1331                                 0, 2);
1332               return;
1333             }
1334           TwoMachinesEvent();
1335         } 
1336       else if (initialMode == EditGame) 
1337         {
1338           EditGameEvent();
1339         } 
1340       else if (initialMode == EditPosition) 
1341         {
1342           EditPositionEvent();
1343         } 
1344       else if (initialMode == Training) 
1345         {
1346           if (*appData.loadGameFile == NULLCHAR) 
1347             {
1348               DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1349               return;
1350             }
1351           TrainingEvent();
1352         }
1353     }
1354   
1355   return;
1356 }
1357
1358 /*
1359  * Establish will establish a contact to a remote host.port.
1360  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1361  *  used to talk to the host.
1362  * Returns 0 if okay, error code if not.
1363  */
1364 int
1365 establish()
1366 {
1367     char buf[MSG_SIZ];
1368
1369     if (*appData.icsCommPort != NULLCHAR) {
1370         /* Talk to the host through a serial comm port */
1371         return OpenCommPort(appData.icsCommPort, &icsPR);
1372
1373     } else if (*appData.gateway != NULLCHAR) {
1374         if (*appData.remoteShell == NULLCHAR) {
1375             /* Use the rcmd protocol to run telnet program on a gateway host */
1376             snprintf(buf, sizeof(buf), "%s %s %s",
1377                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1378             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1379
1380         } else {
1381             /* Use the rsh program to run telnet program on a gateway host */
1382             if (*appData.remoteUser == NULLCHAR) {
1383                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1384                         appData.gateway, appData.telnetProgram,
1385                         appData.icsHost, appData.icsPort);
1386             } else {
1387                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1388                         appData.remoteShell, appData.gateway,
1389                         appData.remoteUser, appData.telnetProgram,
1390                         appData.icsHost, appData.icsPort);
1391             }
1392             return StartChildProcess(buf, "", &icsPR);
1393
1394         }
1395     } else if (appData.useTelnet) {
1396         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1397
1398     } else {
1399         /* TCP socket interface differs somewhat between
1400            Unix and NT; handle details in the front end.
1401            */
1402         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1403     }
1404 }
1405
1406 void
1407 show_bytes(fp, buf, count)
1408      FILE *fp;
1409      char *buf;
1410      int count;
1411 {
1412     while (count--) {
1413         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1414             fprintf(fp, "\\%03o", *buf & 0xff);
1415         } else {
1416             putc(*buf, fp);
1417         }
1418         buf++;
1419     }
1420     fflush(fp);
1421 }
1422
1423 /* Returns an errno value */
1424 int
1425 OutputMaybeTelnet(pr, message, count, outError)
1426      ProcRef pr;
1427      char *message;
1428      int count;
1429      int *outError;
1430 {
1431     char buf[8192], *p, *q, *buflim;
1432     int left, newcount, outcount;
1433
1434     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1435         *appData.gateway != NULLCHAR) {
1436         if (appData.debugMode) {
1437             fprintf(debugFP, ">ICS: ");
1438             show_bytes(debugFP, message, count);
1439             fprintf(debugFP, "\n");
1440         }
1441         return OutputToProcess(pr, message, count, outError);
1442     }
1443
1444     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1445     p = message;
1446     q = buf;
1447     left = count;
1448     newcount = 0;
1449     while (left) {
1450         if (q >= buflim) {
1451             if (appData.debugMode) {
1452                 fprintf(debugFP, ">ICS: ");
1453                 show_bytes(debugFP, buf, newcount);
1454                 fprintf(debugFP, "\n");
1455             }
1456             outcount = OutputToProcess(pr, buf, newcount, outError);
1457             if (outcount < newcount) return -1; /* to be sure */
1458             q = buf;
1459             newcount = 0;
1460         }
1461         if (*p == '\n') {
1462             *q++ = '\r';
1463             newcount++;
1464         } else if (((unsigned char) *p) == TN_IAC) {
1465             *q++ = (char) TN_IAC;
1466             newcount ++;
1467         }
1468         *q++ = *p++;
1469         newcount++;
1470         left--;
1471     }
1472     if (appData.debugMode) {
1473         fprintf(debugFP, ">ICS: ");
1474         show_bytes(debugFP, buf, newcount);
1475         fprintf(debugFP, "\n");
1476     }
1477     outcount = OutputToProcess(pr, buf, newcount, outError);
1478     if (outcount < newcount) return -1; /* to be sure */
1479     return count;
1480 }
1481
1482 void
1483 read_from_player(isr, closure, message, count, error)
1484      InputSourceRef isr;
1485      VOIDSTAR closure;
1486      char *message;
1487      int count;
1488      int error;
1489 {
1490     int outError, outCount;
1491     static int gotEof = 0;
1492
1493     /* Pass data read from player on to ICS */
1494     if (count > 0) {
1495         gotEof = 0;
1496         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1497         if (outCount < count) {
1498             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1499         }
1500     } else if (count < 0) {
1501         RemoveInputSource(isr);
1502         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1503     } else if (gotEof++ > 0) {
1504         RemoveInputSource(isr);
1505         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1506     }
1507 }
1508
1509 void
1510 KeepAlive()
1511 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1512     SendToICS("date\n");
1513     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1514 }
1515
1516 /* added routine for printf style output to ics */
1517 void ics_printf(char *format, ...)
1518 {
1519     char buffer[MSG_SIZ];
1520     va_list args;
1521
1522     va_start(args, format);
1523     vsnprintf(buffer, sizeof(buffer), format, args);
1524     buffer[sizeof(buffer)-1] = '\0';
1525     SendToICS(buffer);
1526     va_end(args);
1527 }
1528
1529 void
1530 SendToICS(s)
1531      char *s;
1532 {
1533     int count, outCount, outError;
1534
1535     if (icsPR == NULL) return;
1536
1537     count = strlen(s);
1538     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1539     if (outCount < count) {
1540         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1541     }
1542 }
1543
1544 /* This is used for sending logon scripts to the ICS. Sending
1545    without a delay causes problems when using timestamp on ICC
1546    (at least on my machine). */
1547 void
1548 SendToICSDelayed(s,msdelay)
1549      char *s;
1550      long msdelay;
1551 {
1552     int count, outCount, outError;
1553
1554     if (icsPR == NULL) return;
1555
1556     count = strlen(s);
1557     if (appData.debugMode) {
1558         fprintf(debugFP, ">ICS: ");
1559         show_bytes(debugFP, s, count);
1560         fprintf(debugFP, "\n");
1561     }
1562     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1563                                       msdelay);
1564     if (outCount < count) {
1565         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1566     }
1567 }
1568
1569
1570 /* Remove all highlighting escape sequences in s
1571    Also deletes any suffix starting with '('
1572    */
1573 char *
1574 StripHighlightAndTitle(s)
1575      char *s;
1576 {
1577     static char retbuf[MSG_SIZ];
1578     char *p = retbuf;
1579
1580     while (*s != NULLCHAR) {
1581         while (*s == '\033') {
1582             while (*s != NULLCHAR && !isalpha(*s)) s++;
1583             if (*s != NULLCHAR) s++;
1584         }
1585         while (*s != NULLCHAR && *s != '\033') {
1586             if (*s == '(' || *s == '[') {
1587                 *p = NULLCHAR;
1588                 return retbuf;
1589             }
1590             *p++ = *s++;
1591         }
1592     }
1593     *p = NULLCHAR;
1594     return retbuf;
1595 }
1596
1597 /* Remove all highlighting escape sequences in s */
1598 char *
1599 StripHighlight(s)
1600      char *s;
1601 {
1602     static char retbuf[MSG_SIZ];
1603     char *p = retbuf;
1604
1605     while (*s != NULLCHAR) {
1606         while (*s == '\033') {
1607             while (*s != NULLCHAR && !isalpha(*s)) s++;
1608             if (*s != NULLCHAR) s++;
1609         }
1610         while (*s != NULLCHAR && *s != '\033') {
1611             *p++ = *s++;
1612         }
1613     }
1614     *p = NULLCHAR;
1615     return retbuf;
1616 }
1617
1618 char *variantNames[] = VARIANT_NAMES;
1619 char *
1620 VariantName(v)
1621      VariantClass v;
1622 {
1623     return variantNames[v];
1624 }
1625
1626
1627 /* Identify a variant from the strings the chess servers use or the
1628    PGN Variant tag names we use. */
1629 VariantClass
1630 StringToVariant(e)
1631      char *e;
1632 {
1633     char *p;
1634     int wnum = -1;
1635     VariantClass v = VariantNormal;
1636     int i, found = FALSE;
1637     char buf[MSG_SIZ];
1638
1639     if (!e) return v;
1640
1641     /* [HGM] skip over optional board-size prefixes */
1642     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1643         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1644         while( *e++ != '_');
1645     }
1646
1647     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1648         v = VariantNormal;
1649         found = TRUE;
1650     } else
1651     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1652       if (StrCaseStr(e, variantNames[i])) {
1653         v = (VariantClass) i;
1654         found = TRUE;
1655         break;
1656       }
1657     }
1658
1659     if (!found) {
1660       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1661           || StrCaseStr(e, "wild/fr")
1662           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1663         v = VariantFischeRandom;
1664       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1665                  (i = 1, p = StrCaseStr(e, "w"))) {
1666         p += i;
1667         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1668         if (isdigit(*p)) {
1669           wnum = atoi(p);
1670         } else {
1671           wnum = -1;
1672         }
1673         switch (wnum) {
1674         case 0: /* FICS only, actually */
1675         case 1:
1676           /* Castling legal even if K starts on d-file */
1677           v = VariantWildCastle;
1678           break;
1679         case 2:
1680         case 3:
1681         case 4:
1682           /* Castling illegal even if K & R happen to start in
1683              normal positions. */
1684           v = VariantNoCastle;
1685           break;
1686         case 5:
1687         case 7:
1688         case 8:
1689         case 10:
1690         case 11:
1691         case 12:
1692         case 13:
1693         case 14:
1694         case 15:
1695         case 18:
1696         case 19:
1697           /* Castling legal iff K & R start in normal positions */
1698           v = VariantNormal;
1699           break;
1700         case 6:
1701         case 20:
1702         case 21:
1703           /* Special wilds for position setup; unclear what to do here */
1704           v = VariantLoadable;
1705           break;
1706         case 9:
1707           /* Bizarre ICC game */
1708           v = VariantTwoKings;
1709           break;
1710         case 16:
1711           v = VariantKriegspiel;
1712           break;
1713         case 17:
1714           v = VariantLosers;
1715           break;
1716         case 22:
1717           v = VariantFischeRandom;
1718           break;
1719         case 23:
1720           v = VariantCrazyhouse;
1721           break;
1722         case 24:
1723           v = VariantBughouse;
1724           break;
1725         case 25:
1726           v = Variant3Check;
1727           break;
1728         case 26:
1729           /* Not quite the same as FICS suicide! */
1730           v = VariantGiveaway;
1731           break;
1732         case 27:
1733           v = VariantAtomic;
1734           break;
1735         case 28:
1736           v = VariantShatranj;
1737           break;
1738
1739         /* Temporary names for future ICC types.  The name *will* change in
1740            the next xboard/WinBoard release after ICC defines it. */
1741         case 29:
1742           v = Variant29;
1743           break;
1744         case 30:
1745           v = Variant30;
1746           break;
1747         case 31:
1748           v = Variant31;
1749           break;
1750         case 32:
1751           v = Variant32;
1752           break;
1753         case 33:
1754           v = Variant33;
1755           break;
1756         case 34:
1757           v = Variant34;
1758           break;
1759         case 35:
1760           v = Variant35;
1761           break;
1762         case 36:
1763           v = Variant36;
1764           break;
1765         case 37:
1766           v = VariantShogi;
1767           break;
1768         case 38:
1769           v = VariantXiangqi;
1770           break;
1771         case 39:
1772           v = VariantCourier;
1773           break;
1774         case 40:
1775           v = VariantGothic;
1776           break;
1777         case 41:
1778           v = VariantCapablanca;
1779           break;
1780         case 42:
1781           v = VariantKnightmate;
1782           break;
1783         case 43:
1784           v = VariantFairy;
1785           break;
1786         case 44:
1787           v = VariantCylinder;
1788           break;
1789         case 45:
1790           v = VariantFalcon;
1791           break;
1792         case 46:
1793           v = VariantCapaRandom;
1794           break;
1795         case 47:
1796           v = VariantBerolina;
1797           break;
1798         case 48:
1799           v = VariantJanus;
1800           break;
1801         case 49:
1802           v = VariantSuper;
1803           break;
1804         case 50:
1805           v = VariantGreat;
1806           break;
1807         case -1:
1808           /* Found "wild" or "w" in the string but no number;
1809              must assume it's normal chess. */
1810           v = VariantNormal;
1811           break;
1812         default:
1813           sprintf(buf, _("Unknown wild type %d"), wnum);
1814           DisplayError(buf, 0);
1815           v = VariantUnknown;
1816           break;
1817         }
1818       }
1819     }
1820     if (appData.debugMode) {
1821       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1822               e, wnum, VariantName(v));
1823     }
1824     return v;
1825 }
1826
1827 static int leftover_start = 0, leftover_len = 0;
1828 char star_match[STAR_MATCH_N][MSG_SIZ];
1829
1830 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1831    advance *index beyond it, and set leftover_start to the new value of
1832    *index; else return FALSE.  If pattern contains the character '*', it
1833    matches any sequence of characters not containing '\r', '\n', or the
1834    character following the '*' (if any), and the matched sequence(s) are
1835    copied into star_match.
1836    */
1837 int
1838 looking_at(buf, index, pattern)
1839      char *buf;
1840      int *index;
1841      char *pattern;
1842 {
1843     char *bufp = &buf[*index], *patternp = pattern;
1844     int star_count = 0;
1845     char *matchp = star_match[0];
1846
1847     for (;;) {
1848         if (*patternp == NULLCHAR) {
1849             *index = leftover_start = bufp - buf;
1850             *matchp = NULLCHAR;
1851             return TRUE;
1852         }
1853         if (*bufp == NULLCHAR) return FALSE;
1854         if (*patternp == '*') {
1855             if (*bufp == *(patternp + 1)) {
1856                 *matchp = NULLCHAR;
1857                 matchp = star_match[++star_count];
1858                 patternp += 2;
1859                 bufp++;
1860                 continue;
1861             } else if (*bufp == '\n' || *bufp == '\r') {
1862                 patternp++;
1863                 if (*patternp == NULLCHAR)
1864                   continue;
1865                 else
1866                   return FALSE;
1867             } else {
1868                 *matchp++ = *bufp++;
1869                 continue;
1870             }
1871         }
1872         if (*patternp != *bufp) return FALSE;
1873         patternp++;
1874         bufp++;
1875     }
1876 }
1877
1878 void
1879 SendToPlayer(data, length)
1880      char *data;
1881      int length;
1882 {
1883     int error, outCount;
1884     outCount = OutputToProcess(NoProc, data, length, &error);
1885     if (outCount < length) {
1886         DisplayFatalError(_("Error writing to display"), error, 1);
1887     }
1888 }
1889
1890 void
1891 PackHolding(packed, holding)
1892      char packed[];
1893      char *holding;
1894 {
1895     char *p = holding;
1896     char *q = packed;
1897     int runlength = 0;
1898     int curr = 9999;
1899     do {
1900         if (*p == curr) {
1901             runlength++;
1902         } else {
1903             switch (runlength) {
1904               case 0:
1905                 break;
1906               case 1:
1907                 *q++ = curr;
1908                 break;
1909               case 2:
1910                 *q++ = curr;
1911                 *q++ = curr;
1912                 break;
1913               default:
1914                 sprintf(q, "%d", runlength);
1915                 while (*q) q++;
1916                 *q++ = curr;
1917                 break;
1918             }
1919             runlength = 1;
1920             curr = *p;
1921         }
1922     } while (*p++);
1923     *q = NULLCHAR;
1924 }
1925
1926 /* Telnet protocol requests from the front end */
1927 void
1928 TelnetRequest(ddww, option)
1929      unsigned char ddww, option;
1930 {
1931     unsigned char msg[3];
1932     int outCount, outError;
1933
1934     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1935
1936     if (appData.debugMode) {
1937         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1938         switch (ddww) {
1939           case TN_DO:
1940             ddwwStr = "DO";
1941             break;
1942           case TN_DONT:
1943             ddwwStr = "DONT";
1944             break;
1945           case TN_WILL:
1946             ddwwStr = "WILL";
1947             break;
1948           case TN_WONT:
1949             ddwwStr = "WONT";
1950             break;
1951           default:
1952             ddwwStr = buf1;
1953             sprintf(buf1, "%d", ddww);
1954             break;
1955         }
1956         switch (option) {
1957           case TN_ECHO:
1958             optionStr = "ECHO";
1959             break;
1960           default:
1961             optionStr = buf2;
1962             sprintf(buf2, "%d", option);
1963             break;
1964         }
1965         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1966     }
1967     msg[0] = TN_IAC;
1968     msg[1] = ddww;
1969     msg[2] = option;
1970     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1971     if (outCount < 3) {
1972         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1973     }
1974 }
1975
1976 void
1977 DoEcho()
1978 {
1979     if (!appData.icsActive) return;
1980     TelnetRequest(TN_DO, TN_ECHO);
1981 }
1982
1983 void
1984 DontEcho()
1985 {
1986     if (!appData.icsActive) return;
1987     TelnetRequest(TN_DONT, TN_ECHO);
1988 }
1989
1990 void
1991 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1992 {
1993     /* put the holdings sent to us by the server on the board holdings area */
1994     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1995     char p;
1996     ChessSquare piece;
1997
1998     if(gameInfo.holdingsWidth < 2)  return;
1999     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2000         return; // prevent overwriting by pre-board holdings
2001
2002     if( (int)lowestPiece >= BlackPawn ) {
2003         holdingsColumn = 0;
2004         countsColumn = 1;
2005         holdingsStartRow = BOARD_HEIGHT-1;
2006         direction = -1;
2007     } else {
2008         holdingsColumn = BOARD_WIDTH-1;
2009         countsColumn = BOARD_WIDTH-2;
2010         holdingsStartRow = 0;
2011         direction = 1;
2012     }
2013
2014     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2015         board[i][holdingsColumn] = EmptySquare;
2016         board[i][countsColumn]   = (ChessSquare) 0;
2017     }
2018     while( (p=*holdings++) != NULLCHAR ) {
2019         piece = CharToPiece( ToUpper(p) );
2020         if(piece == EmptySquare) continue;
2021         /*j = (int) piece - (int) WhitePawn;*/
2022         j = PieceToNumber(piece);
2023         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2024         if(j < 0) continue;               /* should not happen */
2025         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2026         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2027         board[holdingsStartRow+j*direction][countsColumn]++;
2028     }
2029 }
2030
2031
2032 void
2033 VariantSwitch(Board board, VariantClass newVariant)
2034 {
2035    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2036    Board oldBoard;
2037
2038    startedFromPositionFile = FALSE;
2039    if(gameInfo.variant == newVariant) return;
2040
2041    /* [HGM] This routine is called each time an assignment is made to
2042     * gameInfo.variant during a game, to make sure the board sizes
2043     * are set to match the new variant. If that means adding or deleting
2044     * holdings, we shift the playing board accordingly
2045     * This kludge is needed because in ICS observe mode, we get boards
2046     * of an ongoing game without knowing the variant, and learn about the
2047     * latter only later. This can be because of the move list we requested,
2048     * in which case the game history is refilled from the beginning anyway,
2049     * but also when receiving holdings of a crazyhouse game. In the latter
2050     * case we want to add those holdings to the already received position.
2051     */
2052    
2053    if (appData.debugMode) {
2054      fprintf(debugFP, "Switch board from %s to %s\n",
2055              VariantName(gameInfo.variant), VariantName(newVariant));
2056      setbuf(debugFP, NULL);
2057    }
2058    shuffleOpenings = 0;       /* [HGM] shuffle */
2059    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2060    switch(newVariant) 
2061      {
2062      case VariantShogi:
2063        newWidth = 9;  newHeight = 9;
2064        gameInfo.holdingsSize = 7;
2065      case VariantBughouse:
2066      case VariantCrazyhouse:
2067        newHoldingsWidth = 2; break;
2068      case VariantGreat:
2069        newWidth = 10;
2070      case VariantSuper:
2071        newHoldingsWidth = 2;
2072        gameInfo.holdingsSize = 8;
2073        break;
2074      case VariantGothic:
2075      case VariantCapablanca:
2076      case VariantCapaRandom:
2077        newWidth = 10;
2078      default:
2079        newHoldingsWidth = gameInfo.holdingsSize = 0;
2080      };
2081    
2082    if(newWidth  != gameInfo.boardWidth  ||
2083       newHeight != gameInfo.boardHeight ||
2084       newHoldingsWidth != gameInfo.holdingsWidth ) {
2085      
2086      /* shift position to new playing area, if needed */
2087      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2088        for(i=0; i<BOARD_HEIGHT; i++) 
2089          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2090            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2091              board[i][j];
2092        for(i=0; i<newHeight; i++) {
2093          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2094          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2095        }
2096      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2097        for(i=0; i<BOARD_HEIGHT; i++)
2098          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2099            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2100              board[i][j];
2101      }
2102      gameInfo.boardWidth  = newWidth;
2103      gameInfo.boardHeight = newHeight;
2104      gameInfo.holdingsWidth = newHoldingsWidth;
2105      gameInfo.variant = newVariant;
2106      InitDrawingSizes(-2, 0);
2107    } else gameInfo.variant = newVariant;
2108    CopyBoard(oldBoard, board);   // remember correctly formatted board
2109      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2110    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2111 }
2112
2113 static int loggedOn = FALSE;
2114
2115 /*-- Game start info cache: --*/
2116 int gs_gamenum;
2117 char gs_kind[MSG_SIZ];
2118 static char player1Name[128] = "";
2119 static char player2Name[128] = "";
2120 static char cont_seq[] = "\n\\   ";
2121 static int player1Rating = -1;
2122 static int player2Rating = -1;
2123 /*----------------------------*/
2124
2125 ColorClass curColor = ColorNormal;
2126 int suppressKibitz = 0;
2127
2128 void
2129 read_from_ics(isr, closure, data, count, error)
2130      InputSourceRef isr;
2131      VOIDSTAR closure;
2132      char *data;
2133      int count;
2134      int error;
2135 {
2136 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2137 #define STARTED_NONE 0
2138 #define STARTED_MOVES 1
2139 #define STARTED_BOARD 2
2140 #define STARTED_OBSERVE 3
2141 #define STARTED_HOLDINGS 4
2142 #define STARTED_CHATTER 5
2143 #define STARTED_COMMENT 6
2144 #define STARTED_MOVES_NOHIDE 7
2145
2146     static int started = STARTED_NONE;
2147     static char parse[20000];
2148     static int parse_pos = 0;
2149     static char buf[BUF_SIZE + 1];
2150     static int firstTime = TRUE, intfSet = FALSE;
2151     static ColorClass prevColor = ColorNormal;
2152     static int savingComment = FALSE;
2153     static int cmatch = 0; // continuation sequence match
2154     char *bp;
2155     char str[500];
2156     int i, oldi;
2157     int buf_len;
2158     int next_out;
2159     int tkind;
2160     int backup;    /* [DM] For zippy color lines */
2161     char *p;
2162     char talker[MSG_SIZ]; // [HGM] chat
2163     int channel;
2164
2165     if (appData.debugMode) {
2166       if (!error) {
2167         fprintf(debugFP, "<ICS: ");
2168         show_bytes(debugFP, data, count);
2169         fprintf(debugFP, "\n");
2170       }
2171     }
2172
2173     if (appData.debugMode) { int f = forwardMostMove;
2174         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2175                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2176                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2177     }
2178     if (count > 0) {
2179         /* If last read ended with a partial line that we couldn't parse,
2180            prepend it to the new read and try again. */
2181         if (leftover_len > 0) {
2182             for (i=0; i<leftover_len; i++)
2183               buf[i] = buf[leftover_start + i];
2184         }
2185
2186     /* copy new characters into the buffer */
2187     bp = buf + leftover_len;
2188     buf_len=leftover_len;
2189     for (i=0; i<count; i++)
2190     {
2191         // ignore these
2192         if (data[i] == '\r')
2193             continue;
2194
2195         // join lines split by ICS?
2196         if (!appData.noJoin)
2197         {
2198             /*
2199                 Joining just consists of finding matches against the
2200                 continuation sequence, and discarding that sequence
2201                 if found instead of copying it.  So, until a match
2202                 fails, there's nothing to do since it might be the
2203                 complete sequence, and thus, something we don't want
2204                 copied.
2205             */
2206             if (data[i] == cont_seq[cmatch])
2207             {
2208                 cmatch++;
2209                 if (cmatch == strlen(cont_seq))
2210                 {
2211                     cmatch = 0; // complete match.  just reset the counter
2212
2213                     /*
2214                         it's possible for the ICS to not include the space
2215                         at the end of the last word, making our [correct]
2216                         join operation fuse two separate words.  the server
2217                         does this when the space occurs at the width setting.
2218                     */
2219                     if (!buf_len || buf[buf_len-1] != ' ')
2220                     {
2221                         *bp++ = ' ';
2222                         buf_len++;
2223                     }
2224                 }
2225                 continue;
2226             }
2227             else if (cmatch)
2228             {
2229                 /*
2230                     match failed, so we have to copy what matched before
2231                     falling through and copying this character.  In reality,
2232                     this will only ever be just the newline character, but
2233                     it doesn't hurt to be precise.
2234                 */
2235                 strncpy(bp, cont_seq, cmatch);
2236                 bp += cmatch;
2237                 buf_len += cmatch;
2238                 cmatch = 0;
2239             }
2240         }
2241
2242         // copy this char
2243         *bp++ = data[i];
2244         buf_len++;
2245     }
2246
2247         buf[buf_len] = NULLCHAR;
2248 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2249         next_out = 0;
2250         leftover_start = 0;
2251
2252         i = 0;
2253         while (i < buf_len) {
2254             /* Deal with part of the TELNET option negotiation
2255                protocol.  We refuse to do anything beyond the
2256                defaults, except that we allow the WILL ECHO option,
2257                which ICS uses to turn off password echoing when we are
2258                directly connected to it.  We reject this option
2259                if localLineEditing mode is on (always on in xboard)
2260                and we are talking to port 23, which might be a real
2261                telnet server that will try to keep WILL ECHO on permanently.
2262              */
2263             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2264                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2265                 unsigned char option;
2266                 oldi = i;
2267                 switch ((unsigned char) buf[++i]) {
2268                   case TN_WILL:
2269                     if (appData.debugMode)
2270                       fprintf(debugFP, "\n<WILL ");
2271                     switch (option = (unsigned char) buf[++i]) {
2272                       case TN_ECHO:
2273                         if (appData.debugMode)
2274                           fprintf(debugFP, "ECHO ");
2275                         /* Reply only if this is a change, according
2276                            to the protocol rules. */
2277                         if (remoteEchoOption) break;
2278                         if (appData.localLineEditing &&
2279                             atoi(appData.icsPort) == TN_PORT) {
2280                             TelnetRequest(TN_DONT, TN_ECHO);
2281                         } else {
2282                             EchoOff();
2283                             TelnetRequest(TN_DO, TN_ECHO);
2284                             remoteEchoOption = TRUE;
2285                         }
2286                         break;
2287                       default:
2288                         if (appData.debugMode)
2289                           fprintf(debugFP, "%d ", option);
2290                         /* Whatever this is, we don't want it. */
2291                         TelnetRequest(TN_DONT, option);
2292                         break;
2293                     }
2294                     break;
2295                   case TN_WONT:
2296                     if (appData.debugMode)
2297                       fprintf(debugFP, "\n<WONT ");
2298                     switch (option = (unsigned char) buf[++i]) {
2299                       case TN_ECHO:
2300                         if (appData.debugMode)
2301                           fprintf(debugFP, "ECHO ");
2302                         /* Reply only if this is a change, according
2303                            to the protocol rules. */
2304                         if (!remoteEchoOption) break;
2305                         EchoOn();
2306                         TelnetRequest(TN_DONT, TN_ECHO);
2307                         remoteEchoOption = FALSE;
2308                         break;
2309                       default:
2310                         if (appData.debugMode)
2311                           fprintf(debugFP, "%d ", (unsigned char) option);
2312                         /* Whatever this is, it must already be turned
2313                            off, because we never agree to turn on
2314                            anything non-default, so according to the
2315                            protocol rules, we don't reply. */
2316                         break;
2317                     }
2318                     break;
2319                   case TN_DO:
2320                     if (appData.debugMode)
2321                       fprintf(debugFP, "\n<DO ");
2322                     switch (option = (unsigned char) buf[++i]) {
2323                       default:
2324                         /* Whatever this is, we refuse to do it. */
2325                         if (appData.debugMode)
2326                           fprintf(debugFP, "%d ", option);
2327                         TelnetRequest(TN_WONT, option);
2328                         break;
2329                     }
2330                     break;
2331                   case TN_DONT:
2332                     if (appData.debugMode)
2333                       fprintf(debugFP, "\n<DONT ");
2334                     switch (option = (unsigned char) buf[++i]) {
2335                       default:
2336                         if (appData.debugMode)
2337                           fprintf(debugFP, "%d ", option);
2338                         /* Whatever this is, we are already not doing
2339                            it, because we never agree to do anything
2340                            non-default, so according to the protocol
2341                            rules, we don't reply. */
2342                         break;
2343                     }
2344                     break;
2345                   case TN_IAC:
2346                     if (appData.debugMode)
2347                       fprintf(debugFP, "\n<IAC ");
2348                     /* Doubled IAC; pass it through */
2349                     i--;
2350                     break;
2351                   default:
2352                     if (appData.debugMode)
2353                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2354                     /* Drop all other telnet commands on the floor */
2355                     break;
2356                 }
2357                 if (oldi > next_out)
2358                   SendToPlayer(&buf[next_out], oldi - next_out);
2359                 if (++i > next_out)
2360                   next_out = i;
2361                 continue;
2362             }
2363
2364             /* OK, this at least will *usually* work */
2365             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2366                 loggedOn = TRUE;
2367             }
2368
2369             if (loggedOn && !intfSet) {
2370                 if (ics_type == ICS_ICC) {
2371                   sprintf(str,
2372                           "/set-quietly interface %s\n/set-quietly style 12\n",
2373                           programVersion);
2374                 } else if (ics_type == ICS_CHESSNET) {
2375                   sprintf(str, "/style 12\n");
2376                 } else {
2377                   strcpy(str, "alias $ @\n$set interface ");
2378                   strcat(str, programVersion);
2379                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2380 #ifdef WIN32
2381                   strcat(str, "$iset nohighlight 1\n");
2382 #endif
2383                   strcat(str, "$iset lock 1\n$style 12\n");
2384                 }
2385                 SendToICS(str);
2386                 NotifyFrontendLogin();
2387                 intfSet = TRUE;
2388             }
2389
2390             if (started == STARTED_COMMENT) {
2391                 /* Accumulate characters in comment */
2392                 parse[parse_pos++] = buf[i];
2393                 if (buf[i] == '\n') {
2394                     parse[parse_pos] = NULLCHAR;
2395                     if(chattingPartner>=0) {
2396                         char mess[MSG_SIZ];
2397                         sprintf(mess, "%s%s", talker, parse);
2398                         OutputChatMessage(chattingPartner, mess);
2399                         chattingPartner = -1;
2400                     } else
2401                     if(!suppressKibitz) // [HGM] kibitz
2402                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2403                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2404                         int nrDigit = 0, nrAlph = 0, j;
2405                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2406                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2407                         parse[parse_pos] = NULLCHAR;
2408                         // try to be smart: if it does not look like search info, it should go to
2409                         // ICS interaction window after all, not to engine-output window.
2410                         for(j=0; j<parse_pos; j++) { // count letters and digits
2411                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2412                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2413                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2414                         }
2415                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2416                             int depth=0; float score;
2417                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2418                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2419                                 pvInfoList[forwardMostMove-1].depth = depth;
2420                                 pvInfoList[forwardMostMove-1].score = 100*score;
2421                             }
2422                             OutputKibitz(suppressKibitz, parse);
2423                             next_out = i+1; // [HGM] suppress printing in ICS window
2424                         } else {
2425                             char tmp[MSG_SIZ];
2426                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2427                             SendToPlayer(tmp, strlen(tmp));
2428                         }
2429                     }
2430                     started = STARTED_NONE;
2431                 } else {
2432                     /* Don't match patterns against characters in comment */
2433                     i++;
2434                     continue;
2435                 }
2436             }
2437             if (started == STARTED_CHATTER) {
2438                 if (buf[i] != '\n') {
2439                     /* Don't match patterns against characters in chatter */
2440                     i++;
2441                     continue;
2442                 }
2443                 started = STARTED_NONE;
2444             }
2445
2446             /* Kludge to deal with rcmd protocol */
2447             if (firstTime && looking_at(buf, &i, "\001*")) {
2448                 DisplayFatalError(&buf[1], 0, 1);
2449                 continue;
2450             } else {
2451                 firstTime = FALSE;
2452             }
2453
2454             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2455                 ics_type = ICS_ICC;
2456                 ics_prefix = "/";
2457                 if (appData.debugMode)
2458                   fprintf(debugFP, "ics_type %d\n", ics_type);
2459                 continue;
2460             }
2461             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2462                 ics_type = ICS_FICS;
2463                 ics_prefix = "$";
2464                 if (appData.debugMode)
2465                   fprintf(debugFP, "ics_type %d\n", ics_type);
2466                 continue;
2467             }
2468             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2469                 ics_type = ICS_CHESSNET;
2470                 ics_prefix = "/";
2471                 if (appData.debugMode)
2472                   fprintf(debugFP, "ics_type %d\n", ics_type);
2473                 continue;
2474             }
2475
2476             if (!loggedOn &&
2477                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2478                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2479                  looking_at(buf, &i, "will be \"*\""))) {
2480               strcpy(ics_handle, star_match[0]);
2481               continue;
2482             }
2483
2484             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2485               char buf[MSG_SIZ];
2486               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2487               DisplayIcsInteractionTitle(buf);
2488               have_set_title = TRUE;
2489             }
2490
2491             /* skip finger notes */
2492             if (started == STARTED_NONE &&
2493                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2494                  (buf[i] == '1' && buf[i+1] == '0')) &&
2495                 buf[i+2] == ':' && buf[i+3] == ' ') {
2496               started = STARTED_CHATTER;
2497               i += 3;
2498               continue;
2499             }
2500
2501             /* skip formula vars */
2502             if (started == STARTED_NONE &&
2503                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2504               started = STARTED_CHATTER;
2505               i += 3;
2506               continue;
2507             }
2508
2509             oldi = i;
2510             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2511             if (appData.autoKibitz && started == STARTED_NONE &&
2512                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2513                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2514                 if(looking_at(buf, &i, "* kibitzes: ") &&
2515                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2516                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2517                         suppressKibitz = TRUE;
2518                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2519                                 && (gameMode == IcsPlayingWhite)) ||
2520                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2521                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2522                             started = STARTED_CHATTER; // own kibitz we simply discard
2523                         else {
2524                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2525                             parse_pos = 0; parse[0] = NULLCHAR;
2526                             savingComment = TRUE;
2527                             suppressKibitz = gameMode != IcsObserving ? 2 :
2528                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2529                         }
2530                         continue;
2531                 } else
2532                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2533                     // suppress the acknowledgements of our own autoKibitz
2534                     SendToPlayer(star_match[0], strlen(star_match[0]));
2535                     looking_at(buf, &i, "*% "); // eat prompt
2536                     next_out = i;
2537                 }
2538             } // [HGM] kibitz: end of patch
2539
2540 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2541
2542             // [HGM] chat: intercept tells by users for which we have an open chat window
2543             channel = -1;
2544             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2545                                            looking_at(buf, &i, "* whispers:") ||
2546                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2547                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2548                 int p;
2549                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2550                 chattingPartner = -1;
2551
2552                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2553                 for(p=0; p<MAX_CHAT; p++) {
2554                     if(channel == atoi(chatPartner[p])) {
2555                     talker[0] = '['; strcat(talker, "]");
2556                     chattingPartner = p; break;
2557                     }
2558                 } else
2559                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2560                 for(p=0; p<MAX_CHAT; p++) {
2561                     if(!strcmp("WHISPER", chatPartner[p])) {
2562                         talker[0] = '['; strcat(talker, "]");
2563                         chattingPartner = p; break;
2564                     }
2565                 }
2566                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2567                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2568                     talker[0] = 0;
2569                     chattingPartner = p; break;
2570                 }
2571                 if(chattingPartner<0) i = oldi; else {
2572                     started = STARTED_COMMENT;
2573                     parse_pos = 0; parse[0] = NULLCHAR;
2574                     savingComment = TRUE;
2575                     suppressKibitz = TRUE;
2576                 }
2577             } // [HGM] chat: end of patch
2578
2579             if (appData.zippyTalk || appData.zippyPlay) {
2580                 /* [DM] Backup address for color zippy lines */
2581                 backup = i;
2582 #if ZIPPY
2583        #ifdef WIN32
2584                if (loggedOn == TRUE)
2585                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2586                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2587        #else
2588                 if (ZippyControl(buf, &i) ||
2589                     ZippyConverse(buf, &i) ||
2590                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2591                       loggedOn = TRUE;
2592                       if (!appData.colorize) continue;
2593                 }
2594        #endif
2595 #endif
2596             } // [DM] 'else { ' deleted
2597                 if (
2598                     /* Regular tells and says */
2599                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2600                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2601                     looking_at(buf, &i, "* says: ") ||
2602                     /* Don't color "message" or "messages" output */
2603                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2604                     looking_at(buf, &i, "*. * at *:*: ") ||
2605                     looking_at(buf, &i, "--* (*:*): ") ||
2606                     /* Message notifications (same color as tells) */
2607                     looking_at(buf, &i, "* has left a message ") ||
2608                     looking_at(buf, &i, "* just sent you a message:\n") ||
2609                     /* Whispers and kibitzes */
2610                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2611                     looking_at(buf, &i, "* kibitzes: ") ||
2612                     /* Channel tells */
2613                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2614
2615                   if (tkind == 1 && strchr(star_match[0], ':')) {
2616                       /* Avoid "tells you:" spoofs in channels */
2617                      tkind = 3;
2618                   }
2619                   if (star_match[0][0] == NULLCHAR ||
2620                       strchr(star_match[0], ' ') ||
2621                       (tkind == 3 && strchr(star_match[1], ' '))) {
2622                     /* Reject bogus matches */
2623                     i = oldi;
2624                   } else {
2625                     if (appData.colorize) {
2626                       if (oldi > next_out) {
2627                         SendToPlayer(&buf[next_out], oldi - next_out);
2628                         next_out = oldi;
2629                       }
2630                       switch (tkind) {
2631                       case 1:
2632                         Colorize(ColorTell, FALSE);
2633                         curColor = ColorTell;
2634                         break;
2635                       case 2:
2636                         Colorize(ColorKibitz, FALSE);
2637                         curColor = ColorKibitz;
2638                         break;
2639                       case 3:
2640                         p = strrchr(star_match[1], '(');
2641                         if (p == NULL) {
2642                           p = star_match[1];
2643                         } else {
2644                           p++;
2645                         }
2646                         if (atoi(p) == 1) {
2647                           Colorize(ColorChannel1, FALSE);
2648                           curColor = ColorChannel1;
2649                         } else {
2650                           Colorize(ColorChannel, FALSE);
2651                           curColor = ColorChannel;
2652                         }
2653                         break;
2654                       case 5:
2655                         curColor = ColorNormal;
2656                         break;
2657                       }
2658                     }
2659                     if (started == STARTED_NONE && appData.autoComment &&
2660                         (gameMode == IcsObserving ||
2661                          gameMode == IcsPlayingWhite ||
2662                          gameMode == IcsPlayingBlack)) {
2663                       parse_pos = i - oldi;
2664                       memcpy(parse, &buf[oldi], parse_pos);
2665                       parse[parse_pos] = NULLCHAR;
2666                       started = STARTED_COMMENT;
2667                       savingComment = TRUE;
2668                     } else {
2669                       started = STARTED_CHATTER;
2670                       savingComment = FALSE;
2671                     }
2672                     loggedOn = TRUE;
2673                     continue;
2674                   }
2675                 }
2676
2677                 if (looking_at(buf, &i, "* s-shouts: ") ||
2678                     looking_at(buf, &i, "* c-shouts: ")) {
2679                     if (appData.colorize) {
2680                         if (oldi > next_out) {
2681                             SendToPlayer(&buf[next_out], oldi - next_out);
2682                             next_out = oldi;
2683                         }
2684                         Colorize(ColorSShout, FALSE);
2685                         curColor = ColorSShout;
2686                     }
2687                     loggedOn = TRUE;
2688                     started = STARTED_CHATTER;
2689                     continue;
2690                 }
2691
2692                 if (looking_at(buf, &i, "--->")) {
2693                     loggedOn = TRUE;
2694                     continue;
2695                 }
2696
2697                 if (looking_at(buf, &i, "* shouts: ") ||
2698                     looking_at(buf, &i, "--> ")) {
2699                     if (appData.colorize) {
2700                         if (oldi > next_out) {
2701                             SendToPlayer(&buf[next_out], oldi - next_out);
2702                             next_out = oldi;
2703                         }
2704                         Colorize(ColorShout, FALSE);
2705                         curColor = ColorShout;
2706                     }
2707                     loggedOn = TRUE;
2708                     started = STARTED_CHATTER;
2709                     continue;
2710                 }
2711
2712                 if (looking_at( buf, &i, "Challenge:")) {
2713                     if (appData.colorize) {
2714                         if (oldi > next_out) {
2715                             SendToPlayer(&buf[next_out], oldi - next_out);
2716                             next_out = oldi;
2717                         }
2718                         Colorize(ColorChallenge, FALSE);
2719                         curColor = ColorChallenge;
2720                     }
2721                     loggedOn = TRUE;
2722                     continue;
2723                 }
2724
2725                 if (looking_at(buf, &i, "* offers you") ||
2726                     looking_at(buf, &i, "* offers to be") ||
2727                     looking_at(buf, &i, "* would like to") ||
2728                     looking_at(buf, &i, "* requests to") ||
2729                     looking_at(buf, &i, "Your opponent offers") ||
2730                     looking_at(buf, &i, "Your opponent requests")) {
2731
2732                     if (appData.colorize) {
2733                         if (oldi > next_out) {
2734                             SendToPlayer(&buf[next_out], oldi - next_out);
2735                             next_out = oldi;
2736                         }
2737                         Colorize(ColorRequest, FALSE);
2738                         curColor = ColorRequest;
2739                     }
2740                     continue;
2741                 }
2742
2743                 if (looking_at(buf, &i, "* (*) seeking")) {
2744                     if (appData.colorize) {
2745                         if (oldi > next_out) {
2746                             SendToPlayer(&buf[next_out], oldi - next_out);
2747                             next_out = oldi;
2748                         }
2749                         Colorize(ColorSeek, FALSE);
2750                         curColor = ColorSeek;
2751                     }
2752                     continue;
2753             }
2754
2755             if (looking_at(buf, &i, "\\   ")) {
2756                 if (prevColor != ColorNormal) {
2757                     if (oldi > next_out) {
2758                         SendToPlayer(&buf[next_out], oldi - next_out);
2759                         next_out = oldi;
2760                     }
2761                     Colorize(prevColor, TRUE);
2762                     curColor = prevColor;
2763                 }
2764                 if (savingComment) {
2765                     parse_pos = i - oldi;
2766                     memcpy(parse, &buf[oldi], parse_pos);
2767                     parse[parse_pos] = NULLCHAR;
2768                     started = STARTED_COMMENT;
2769                 } else {
2770                     started = STARTED_CHATTER;
2771                 }
2772                 continue;
2773             }
2774
2775             if (looking_at(buf, &i, "Black Strength :") ||
2776                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2777                 looking_at(buf, &i, "<10>") ||
2778                 looking_at(buf, &i, "#@#")) {
2779                 /* Wrong board style */
2780                 loggedOn = TRUE;
2781                 SendToICS(ics_prefix);
2782                 SendToICS("set style 12\n");
2783                 SendToICS(ics_prefix);
2784                 SendToICS("refresh\n");
2785                 continue;
2786             }
2787
2788             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2789                 ICSInitScript();
2790                 have_sent_ICS_logon = 1;
2791                 continue;
2792             }
2793
2794             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2795                 (looking_at(buf, &i, "\n<12> ") ||
2796                  looking_at(buf, &i, "<12> "))) {
2797                 loggedOn = TRUE;
2798                 if (oldi > next_out) {
2799                     SendToPlayer(&buf[next_out], oldi - next_out);
2800                 }
2801                 next_out = i;
2802                 started = STARTED_BOARD;
2803                 parse_pos = 0;
2804                 continue;
2805             }
2806
2807             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2808                 looking_at(buf, &i, "<b1> ")) {
2809                 if (oldi > next_out) {
2810                     SendToPlayer(&buf[next_out], oldi - next_out);
2811                 }
2812                 next_out = i;
2813                 started = STARTED_HOLDINGS;
2814                 parse_pos = 0;
2815                 continue;
2816             }
2817
2818             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2819                 loggedOn = TRUE;
2820                 /* Header for a move list -- first line */
2821
2822                 switch (ics_getting_history) {
2823                   case H_FALSE:
2824                     switch (gameMode) {
2825                       case IcsIdle:
2826                       case BeginningOfGame:
2827                         /* User typed "moves" or "oldmoves" while we
2828                            were idle.  Pretend we asked for these
2829                            moves and soak them up so user can step
2830                            through them and/or save them.
2831                            */
2832                         Reset(FALSE, TRUE);
2833                         gameMode = IcsObserving;
2834                         ModeHighlight();
2835                         ics_gamenum = -1;
2836                         ics_getting_history = H_GOT_UNREQ_HEADER;
2837                         break;
2838                       case EditGame: /*?*/
2839                       case EditPosition: /*?*/
2840                         /* Should above feature work in these modes too? */
2841                         /* For now it doesn't */
2842                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2843                         break;
2844                       default:
2845                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2846                         break;
2847                     }
2848                     break;
2849                   case H_REQUESTED:
2850                     /* Is this the right one? */
2851                     if (gameInfo.white && gameInfo.black &&
2852                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2853                         strcmp(gameInfo.black, star_match[2]) == 0) {
2854                         /* All is well */
2855                         ics_getting_history = H_GOT_REQ_HEADER;
2856                     }
2857                     break;
2858                   case H_GOT_REQ_HEADER:
2859                   case H_GOT_UNREQ_HEADER:
2860                   case H_GOT_UNWANTED_HEADER:
2861                   case H_GETTING_MOVES:
2862                     /* Should not happen */
2863                     DisplayError(_("Error gathering move list: two headers"), 0);
2864                     ics_getting_history = H_FALSE;
2865                     break;
2866                 }
2867
2868                 /* Save player ratings into gameInfo if needed */
2869                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2870                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2871                     (gameInfo.whiteRating == -1 ||
2872                      gameInfo.blackRating == -1)) {
2873
2874                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2875                     gameInfo.blackRating = string_to_rating(star_match[3]);
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2878                               gameInfo.whiteRating, gameInfo.blackRating);
2879                 }
2880                 continue;
2881             }
2882
2883             if (looking_at(buf, &i,
2884               "* * match, initial time: * minute*, increment: * second")) {
2885                 /* Header for a move list -- second line */
2886                 /* Initial board will follow if this is a wild game */
2887                 if (gameInfo.event != NULL) free(gameInfo.event);
2888                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2889                 gameInfo.event = StrSave(str);
2890                 /* [HGM] we switched variant. Translate boards if needed. */
2891                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2892                 continue;
2893             }
2894
2895             if (looking_at(buf, &i, "Move  ")) {
2896                 /* Beginning of a move list */
2897                 switch (ics_getting_history) {
2898                   case H_FALSE:
2899                     /* Normally should not happen */
2900                     /* Maybe user hit reset while we were parsing */
2901                     break;
2902                   case H_REQUESTED:
2903                     /* Happens if we are ignoring a move list that is not
2904                      * the one we just requested.  Common if the user
2905                      * tries to observe two games without turning off
2906                      * getMoveList */
2907                     break;
2908                   case H_GETTING_MOVES:
2909                     /* Should not happen */
2910                     DisplayError(_("Error gathering move list: nested"), 0);
2911                     ics_getting_history = H_FALSE;
2912                     break;
2913                   case H_GOT_REQ_HEADER:
2914                     ics_getting_history = H_GETTING_MOVES;
2915                     started = STARTED_MOVES;
2916                     parse_pos = 0;
2917                     if (oldi > next_out) {
2918                         SendToPlayer(&buf[next_out], oldi - next_out);
2919                     }
2920                     break;
2921                   case H_GOT_UNREQ_HEADER:
2922                     ics_getting_history = H_GETTING_MOVES;
2923                     started = STARTED_MOVES_NOHIDE;
2924                     parse_pos = 0;
2925                     break;
2926                   case H_GOT_UNWANTED_HEADER:
2927                     ics_getting_history = H_FALSE;
2928                     break;
2929                 }
2930                 continue;
2931             }
2932
2933             if (looking_at(buf, &i, "% ") ||
2934                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2935                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2936                 if(suppressKibitz) next_out = i;
2937                 savingComment = FALSE;
2938                 suppressKibitz = 0;
2939                 switch (started) {
2940                   case STARTED_MOVES:
2941                   case STARTED_MOVES_NOHIDE:
2942                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2943                     parse[parse_pos + i - oldi] = NULLCHAR;
2944                     ParseGameHistory(parse);
2945 #if ZIPPY
2946                     if (appData.zippyPlay && first.initDone) {
2947                         FeedMovesToProgram(&first, forwardMostMove);
2948                         if (gameMode == IcsPlayingWhite) {
2949                             if (WhiteOnMove(forwardMostMove)) {
2950                                 if (first.sendTime) {
2951                                   if (first.useColors) {
2952                                     SendToProgram("black\n", &first);
2953                                   }
2954                                   SendTimeRemaining(&first, TRUE);
2955                                 }
2956                                 if (first.useColors) {
2957                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2958                                 }
2959                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2960                                 first.maybeThinking = TRUE;
2961                             } else {
2962                                 if (first.usePlayother) {
2963                                   if (first.sendTime) {
2964                                     SendTimeRemaining(&first, TRUE);
2965                                   }
2966                                   SendToProgram("playother\n", &first);
2967                                   firstMove = FALSE;
2968                                 } else {
2969                                   firstMove = TRUE;
2970                                 }
2971                             }
2972                         } else if (gameMode == IcsPlayingBlack) {
2973                             if (!WhiteOnMove(forwardMostMove)) {
2974                                 if (first.sendTime) {
2975                                   if (first.useColors) {
2976                                     SendToProgram("white\n", &first);
2977                                   }
2978                                   SendTimeRemaining(&first, FALSE);
2979                                 }
2980                                 if (first.useColors) {
2981                                   SendToProgram("black\n", &first);
2982                                 }
2983                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2984                                 first.maybeThinking = TRUE;
2985                             } else {
2986                                 if (first.usePlayother) {
2987                                   if (first.sendTime) {
2988                                     SendTimeRemaining(&first, FALSE);
2989                                   }
2990                                   SendToProgram("playother\n", &first);
2991                                   firstMove = FALSE;
2992                                 } else {
2993                                   firstMove = TRUE;
2994                                 }
2995                             }
2996                         }
2997                     }
2998 #endif
2999                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3000                         /* Moves came from oldmoves or moves command
3001                            while we weren't doing anything else.
3002                            */
3003                         currentMove = forwardMostMove;
3004                         ClearHighlights();/*!!could figure this out*/
3005                         flipView = appData.flipView;
3006                         DrawPosition(TRUE, boards[currentMove]);
3007                         DisplayBothClocks();
3008                         sprintf(str, "%s vs. %s",
3009                                 gameInfo.white, gameInfo.black);
3010                         DisplayTitle(str);
3011                         gameMode = IcsIdle;
3012                     } else {
3013                         /* Moves were history of an active game */
3014                         if (gameInfo.resultDetails != NULL) {
3015                             free(gameInfo.resultDetails);
3016                             gameInfo.resultDetails = NULL;
3017                         }
3018                     }
3019                     HistorySet(parseList, backwardMostMove,
3020                                forwardMostMove, currentMove-1);
3021                     DisplayMove(currentMove - 1);
3022                     if (started == STARTED_MOVES) next_out = i;
3023                     started = STARTED_NONE;
3024                     ics_getting_history = H_FALSE;
3025                     break;
3026
3027                   case STARTED_OBSERVE:
3028                     started = STARTED_NONE;
3029                     SendToICS(ics_prefix);
3030                     SendToICS("refresh\n");
3031                     break;
3032
3033                   default:
3034                     break;
3035                 }
3036                 if(bookHit) { // [HGM] book: simulate book reply
3037                     static char bookMove[MSG_SIZ]; // a bit generous?
3038
3039                     programStats.nodes = programStats.depth = programStats.time =
3040                     programStats.score = programStats.got_only_move = 0;
3041                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3042
3043                     strcpy(bookMove, "move ");
3044                     strcat(bookMove, bookHit);
3045                     HandleMachineMove(bookMove, &first);
3046                 }
3047                 continue;
3048             }
3049
3050             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3051                  started == STARTED_HOLDINGS ||
3052                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3053                 /* Accumulate characters in move list or board */
3054                 parse[parse_pos++] = buf[i];
3055             }
3056
3057             /* Start of game messages.  Mostly we detect start of game
3058                when the first board image arrives.  On some versions
3059                of the ICS, though, we need to do a "refresh" after starting
3060                to observe in order to get the current board right away. */
3061             if (looking_at(buf, &i, "Adding game * to observation list")) {
3062                 started = STARTED_OBSERVE;
3063                 continue;
3064             }
3065
3066             /* Handle auto-observe */
3067             if (appData.autoObserve &&
3068                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3069                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3070                 char *player;
3071                 /* Choose the player that was highlighted, if any. */
3072                 if (star_match[0][0] == '\033' ||
3073                     star_match[1][0] != '\033') {
3074                     player = star_match[0];
3075                 } else {
3076                     player = star_match[2];
3077                 }
3078                 sprintf(str, "%sobserve %s\n",
3079                         ics_prefix, StripHighlightAndTitle(player));
3080                 SendToICS(str);
3081
3082                 /* Save ratings from notify string */
3083                 strcpy(player1Name, star_match[0]);
3084                 player1Rating = string_to_rating(star_match[1]);
3085                 strcpy(player2Name, star_match[2]);
3086                 player2Rating = string_to_rating(star_match[3]);
3087
3088                 if (appData.debugMode)
3089                   fprintf(debugFP,
3090                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3091                           player1Name, player1Rating,
3092                           player2Name, player2Rating);
3093
3094                 continue;
3095             }
3096
3097             /* Deal with automatic examine mode after a game,
3098                and with IcsObserving -> IcsExamining transition */
3099             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3100                 looking_at(buf, &i, "has made you an examiner of game *")) {
3101
3102                 int gamenum = atoi(star_match[0]);
3103                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3104                     gamenum == ics_gamenum) {
3105                     /* We were already playing or observing this game;
3106                        no need to refetch history */
3107                     gameMode = IcsExamining;
3108                     if (pausing) {
3109                         pauseExamForwardMostMove = forwardMostMove;
3110                     } else if (currentMove < forwardMostMove) {
3111                         ForwardInner(forwardMostMove);
3112                     }
3113                 } else {
3114                     /* I don't think this case really can happen */
3115                     SendToICS(ics_prefix);
3116                     SendToICS("refresh\n");
3117                 }
3118                 continue;
3119             }
3120
3121             /* Error messages */
3122 //          if (ics_user_moved) {
3123             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3124                 if (looking_at(buf, &i, "Illegal move") ||
3125                     looking_at(buf, &i, "Not a legal move") ||
3126                     looking_at(buf, &i, "Your king is in check") ||
3127                     looking_at(buf, &i, "It isn't your turn") ||
3128                     looking_at(buf, &i, "It is not your move")) {
3129                     /* Illegal move */
3130                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3131                         currentMove = --forwardMostMove;
3132                         DisplayMove(currentMove - 1); /* before DMError */
3133                         DrawPosition(FALSE, boards[currentMove]);
3134                         SwitchClocks();
3135                         DisplayBothClocks();
3136                     }
3137                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3138                     ics_user_moved = 0;
3139                     continue;
3140                 }
3141             }
3142
3143             if (looking_at(buf, &i, "still have time") ||
3144                 looking_at(buf, &i, "not out of time") ||
3145                 looking_at(buf, &i, "either player is out of time") ||
3146                 looking_at(buf, &i, "has timeseal; checking")) {
3147                 /* We must have called his flag a little too soon */
3148                 whiteFlag = blackFlag = FALSE;
3149                 continue;
3150             }
3151
3152             if (looking_at(buf, &i, "added * seconds to") ||
3153                 looking_at(buf, &i, "seconds were added to")) {
3154                 /* Update the clocks */
3155                 SendToICS(ics_prefix);
3156                 SendToICS("refresh\n");
3157                 continue;
3158             }
3159
3160             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3161                 ics_clock_paused = TRUE;
3162                 StopClocks();
3163                 continue;
3164             }
3165
3166             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3167                 ics_clock_paused = FALSE;
3168                 StartClocks();
3169                 continue;
3170             }
3171
3172             /* Grab player ratings from the Creating: message.
3173                Note we have to check for the special case when
3174                the ICS inserts things like [white] or [black]. */
3175             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3176                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3177                 /* star_matches:
3178                    0    player 1 name (not necessarily white)
3179                    1    player 1 rating
3180                    2    empty, white, or black (IGNORED)
3181                    3    player 2 name (not necessarily black)
3182                    4    player 2 rating
3183
3184                    The names/ratings are sorted out when the game
3185                    actually starts (below).
3186                 */
3187                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3188                 player1Rating = string_to_rating(star_match[1]);
3189                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3190                 player2Rating = string_to_rating(star_match[4]);
3191
3192                 if (appData.debugMode)
3193                   fprintf(debugFP,
3194                           "Ratings from 'Creating:' %s %d, %s %d\n",
3195                           player1Name, player1Rating,
3196                           player2Name, player2Rating);
3197
3198                 continue;
3199             }
3200
3201             /* Improved generic start/end-of-game messages */
3202             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3203                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3204                 /* If tkind == 0: */
3205                 /* star_match[0] is the game number */
3206                 /*           [1] is the white player's name */
3207                 /*           [2] is the black player's name */
3208                 /* For end-of-game: */
3209                 /*           [3] is the reason for the game end */
3210                 /*           [4] is a PGN end game-token, preceded by " " */
3211                 /* For start-of-game: */
3212                 /*           [3] begins with "Creating" or "Continuing" */
3213                 /*           [4] is " *" or empty (don't care). */
3214                 int gamenum = atoi(star_match[0]);
3215                 char *whitename, *blackname, *why, *endtoken;
3216                 ChessMove endtype = (ChessMove) 0;
3217
3218                 if (tkind == 0) {
3219                   whitename = star_match[1];
3220                   blackname = star_match[2];
3221                   why = star_match[3];
3222                   endtoken = star_match[4];
3223                 } else {
3224                   whitename = star_match[1];
3225                   blackname = star_match[3];
3226                   why = star_match[5];
3227                   endtoken = star_match[6];
3228                 }
3229
3230                 /* Game start messages */
3231                 if (strncmp(why, "Creating ", 9) == 0 ||
3232                     strncmp(why, "Continuing ", 11) == 0) {
3233                     gs_gamenum = gamenum;
3234                     strcpy(gs_kind, strchr(why, ' ') + 1);
3235                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3236 #if ZIPPY
3237                     if (appData.zippyPlay) {
3238                         ZippyGameStart(whitename, blackname);
3239                     }
3240 #endif /*ZIPPY*/
3241                     continue;
3242                 }
3243
3244                 /* Game end messages */
3245                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3246                     ics_gamenum != gamenum) {
3247                     continue;
3248                 }
3249                 while (endtoken[0] == ' ') endtoken++;
3250                 switch (endtoken[0]) {
3251                   case '*':
3252                   default:
3253                     endtype = GameUnfinished;
3254                     break;
3255                   case '0':
3256                     endtype = BlackWins;
3257                     break;
3258                   case '1':
3259                     if (endtoken[1] == '/')
3260                       endtype = GameIsDrawn;
3261                     else
3262                       endtype = WhiteWins;
3263                     break;
3264                 }
3265                 GameEnds(endtype, why, GE_ICS);
3266 #if ZIPPY
3267                 if (appData.zippyPlay && first.initDone) {
3268                     ZippyGameEnd(endtype, why);
3269                     if (first.pr == NULL) {
3270                       /* Start the next process early so that we'll
3271                          be ready for the next challenge */
3272                       StartChessProgram(&first);
3273                     }
3274                     /* Send "new" early, in case this command takes
3275                        a long time to finish, so that we'll be ready
3276                        for the next challenge. */
3277                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3278                     Reset(TRUE, TRUE);
3279                 }
3280 #endif /*ZIPPY*/
3281                 continue;
3282             }
3283
3284             if (looking_at(buf, &i, "Removing game * from observation") ||
3285                 looking_at(buf, &i, "no longer observing game *") ||
3286                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3287                 if (gameMode == IcsObserving &&
3288                     atoi(star_match[0]) == ics_gamenum)
3289                   {
3290                       /* icsEngineAnalyze */
3291                       if (appData.icsEngineAnalyze) {
3292                             ExitAnalyzeMode();
3293                             ModeHighlight();
3294                       }
3295                       StopClocks();
3296                       gameMode = IcsIdle;
3297                       ics_gamenum = -1;
3298                       ics_user_moved = FALSE;
3299                   }
3300                 continue;
3301             }
3302
3303             if (looking_at(buf, &i, "no longer examining game *")) {
3304                 if (gameMode == IcsExamining &&
3305                     atoi(star_match[0]) == ics_gamenum)
3306                   {
3307                       gameMode = IcsIdle;
3308                       ics_gamenum = -1;
3309                       ics_user_moved = FALSE;
3310                   }
3311                 continue;
3312             }
3313
3314             /* Advance leftover_start past any newlines we find,
3315                so only partial lines can get reparsed */
3316             if (looking_at(buf, &i, "\n")) {
3317                 prevColor = curColor;
3318                 if (curColor != ColorNormal) {
3319                     if (oldi > next_out) {
3320                         SendToPlayer(&buf[next_out], oldi - next_out);
3321                         next_out = oldi;
3322                     }
3323                     Colorize(ColorNormal, FALSE);
3324                     curColor = ColorNormal;
3325                 }
3326                 if (started == STARTED_BOARD) {
3327                     started = STARTED_NONE;
3328                     parse[parse_pos] = NULLCHAR;
3329                     ParseBoard12(parse);
3330                     ics_user_moved = 0;
3331
3332                     /* Send premove here */
3333                     if (appData.premove) {
3334                       char str[MSG_SIZ];
3335                       if (currentMove == 0 &&
3336                           gameMode == IcsPlayingWhite &&
3337                           appData.premoveWhite) {
3338                         sprintf(str, "%s\n", appData.premoveWhiteText);
3339                         if (appData.debugMode)
3340                           fprintf(debugFP, "Sending premove:\n");
3341                         SendToICS(str);
3342                       } else if (currentMove == 1 &&
3343                                  gameMode == IcsPlayingBlack &&
3344                                  appData.premoveBlack) {
3345                         sprintf(str, "%s\n", appData.premoveBlackText);
3346                         if (appData.debugMode)
3347                           fprintf(debugFP, "Sending premove:\n");
3348                         SendToICS(str);
3349                       } else if (gotPremove) {
3350                         gotPremove = 0;
3351                         ClearPremoveHighlights();
3352                         if (appData.debugMode)
3353                           fprintf(debugFP, "Sending premove:\n");
3354                           UserMoveEvent(premoveFromX, premoveFromY,
3355                                         premoveToX, premoveToY,
3356                                         premovePromoChar);
3357                       }
3358                     }
3359
3360                     /* Usually suppress following prompt */
3361                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3362                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3363                         if (looking_at(buf, &i, "*% ")) {
3364                             savingComment = FALSE;
3365                             suppressKibitz = 0;
3366                         }
3367                     }
3368                     next_out = i;
3369                 } else if (started == STARTED_HOLDINGS) {
3370                     int gamenum;
3371                     char new_piece[MSG_SIZ];
3372                     started = STARTED_NONE;
3373                     parse[parse_pos] = NULLCHAR;
3374                     if (appData.debugMode)
3375                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3376                                                         parse, currentMove);
3377                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3378                         gamenum == ics_gamenum) {
3379                         if (gameInfo.variant == VariantNormal) {
3380                           /* [HGM] We seem to switch variant during a game!
3381                            * Presumably no holdings were displayed, so we have
3382                            * to move the position two files to the right to
3383                            * create room for them!
3384                            */
3385                           VariantClass newVariant;
3386                           switch(gameInfo.boardWidth) { // base guess on board width
3387                                 case 9:  newVariant = VariantShogi; break;
3388                                 case 10: newVariant = VariantGreat; break;
3389                                 default: newVariant = VariantCrazyhouse; break;
3390                           }
3391                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3392                           /* Get a move list just to see the header, which
3393                              will tell us whether this is really bug or zh */
3394                           if (ics_getting_history == H_FALSE) {
3395                             ics_getting_history = H_REQUESTED;
3396                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3397                             SendToICS(str);
3398                           }
3399                         }
3400                         new_piece[0] = NULLCHAR;
3401                         sscanf(parse, "game %d white [%s black [%s <- %s",
3402                                &gamenum, white_holding, black_holding,
3403                                new_piece);
3404                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3405                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3406                         /* [HGM] copy holdings to board holdings area */
3407                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3408                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3409                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3410 #if ZIPPY
3411                         if (appData.zippyPlay && first.initDone) {
3412                             ZippyHoldings(white_holding, black_holding,
3413                                           new_piece);
3414                         }
3415 #endif /*ZIPPY*/
3416                         if (tinyLayout || smallLayout) {
3417                             char wh[16], bh[16];
3418                             PackHolding(wh, white_holding);
3419                             PackHolding(bh, black_holding);
3420                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3421                                     gameInfo.white, gameInfo.black);
3422                         } else {
3423                             sprintf(str, "%s [%s] vs. %s [%s]",
3424                                     gameInfo.white, white_holding,
3425                                     gameInfo.black, black_holding);
3426                         }
3427
3428                         DrawPosition(FALSE, boards[currentMove]);
3429                         DisplayTitle(str);
3430                     }
3431                     /* Suppress following prompt */
3432                     if (looking_at(buf, &i, "*% ")) {
3433                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3434                         savingComment = FALSE;
3435                         suppressKibitz = 0;
3436                     }
3437                     next_out = i;
3438                 }
3439                 continue;
3440             }
3441
3442             i++;                /* skip unparsed character and loop back */
3443         }
3444         
3445         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3446 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3447 //          SendToPlayer(&buf[next_out], i - next_out);
3448             started != STARTED_HOLDINGS && leftover_start > next_out) {
3449             SendToPlayer(&buf[next_out], leftover_start - next_out);
3450             next_out = i;
3451         }
3452
3453         leftover_len = buf_len - leftover_start;
3454         /* if buffer ends with something we couldn't parse,
3455            reparse it after appending the next read */
3456
3457     } else if (count == 0) {
3458         RemoveInputSource(isr);
3459         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3460     } else {
3461         DisplayFatalError(_("Error reading from ICS"), error, 1);
3462     }
3463 }
3464
3465
3466 /* Board style 12 looks like this:
3467
3468    <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
3469
3470  * The "<12> " is stripped before it gets to this routine.  The two
3471  * trailing 0's (flip state and clock ticking) are later addition, and
3472  * some chess servers may not have them, or may have only the first.
3473  * Additional trailing fields may be added in the future.
3474  */
3475
3476 #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"
3477
3478 #define RELATION_OBSERVING_PLAYED    0
3479 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3480 #define RELATION_PLAYING_MYMOVE      1
3481 #define RELATION_PLAYING_NOTMYMOVE  -1
3482 #define RELATION_EXAMINING           2
3483 #define RELATION_ISOLATED_BOARD     -3
3484 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3485
3486 void
3487 ParseBoard12(string)
3488      char *string;
3489 {
3490     GameMode newGameMode;
3491     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3492     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3493     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3494     char to_play, board_chars[200];
3495     char move_str[500], str[500], elapsed_time[500];
3496     char black[32], white[32];
3497     Board board;
3498     int prevMove = currentMove;
3499     int ticking = 2;
3500     ChessMove moveType;
3501     int fromX, fromY, toX, toY;
3502     char promoChar;
3503     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3504     char *bookHit = NULL; // [HGM] book
3505     Boolean weird = FALSE, reqFlag = FALSE;
3506
3507     fromX = fromY = toX = toY = -1;
3508
3509     newGame = FALSE;
3510
3511     if (appData.debugMode)
3512       fprintf(debugFP, _("Parsing board: %s\n"), string);
3513
3514     move_str[0] = NULLCHAR;
3515     elapsed_time[0] = NULLCHAR;
3516     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3517         int  i = 0, j;
3518         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3519             if(string[i] == ' ') { ranks++; files = 0; }
3520             else files++;
3521             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3522             i++;
3523         }
3524         for(j = 0; j <i; j++) board_chars[j] = string[j];
3525         board_chars[i] = '\0';
3526         string += i + 1;
3527     }
3528     n = sscanf(string, PATTERN, &to_play, &double_push,
3529                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3530                &gamenum, white, black, &relation, &basetime, &increment,
3531                &white_stren, &black_stren, &white_time, &black_time,
3532                &moveNum, str, elapsed_time, move_str, &ics_flip,
3533                &ticking);
3534
3535     if (n < 21) {
3536         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3537         DisplayError(str, 0);
3538         return;
3539     }
3540
3541     /* Convert the move number to internal form */
3542     moveNum = (moveNum - 1) * 2;
3543     if (to_play == 'B') moveNum++;
3544     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3545       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3546                         0, 1);
3547       return;
3548     }
3549
3550     switch (relation) {
3551       case RELATION_OBSERVING_PLAYED:
3552       case RELATION_OBSERVING_STATIC:
3553         if (gamenum == -1) {
3554             /* Old ICC buglet */
3555             relation = RELATION_OBSERVING_STATIC;
3556         }
3557         newGameMode = IcsObserving;
3558         break;
3559       case RELATION_PLAYING_MYMOVE:
3560       case RELATION_PLAYING_NOTMYMOVE:
3561         newGameMode =
3562           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3563             IcsPlayingWhite : IcsPlayingBlack;
3564         break;
3565       case RELATION_EXAMINING:
3566         newGameMode = IcsExamining;
3567         break;
3568       case RELATION_ISOLATED_BOARD:
3569       default:
3570         /* Just display this board.  If user was doing something else,
3571            we will forget about it until the next board comes. */
3572         newGameMode = IcsIdle;
3573         break;
3574       case RELATION_STARTING_POSITION:
3575         newGameMode = gameMode;
3576         break;
3577     }
3578
3579     /* Modify behavior for initial board display on move listing
3580        of wild games.
3581        */
3582     switch (ics_getting_history) {
3583       case H_FALSE:
3584       case H_REQUESTED:
3585         break;
3586       case H_GOT_REQ_HEADER:
3587       case H_GOT_UNREQ_HEADER:
3588         /* This is the initial position of the current game */
3589         gamenum = ics_gamenum;
3590         moveNum = 0;            /* old ICS bug workaround */
3591         if (to_play == 'B') {
3592           startedFromSetupPosition = TRUE;
3593           blackPlaysFirst = TRUE;
3594           moveNum = 1;
3595           if (forwardMostMove == 0) forwardMostMove = 1;
3596           if (backwardMostMove == 0) backwardMostMove = 1;
3597           if (currentMove == 0) currentMove = 1;
3598         }
3599         newGameMode = gameMode;
3600         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3601         break;
3602       case H_GOT_UNWANTED_HEADER:
3603         /* This is an initial board that we don't want */
3604         return;
3605       case H_GETTING_MOVES:
3606         /* Should not happen */
3607         DisplayError(_("Error gathering move list: extra board"), 0);
3608         ics_getting_history = H_FALSE;
3609         return;
3610     }
3611
3612    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3613                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3614      /* [HGM] We seem to have switched variant unexpectedly
3615       * Try to guess new variant from board size
3616       */
3617           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3618           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3619           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3620           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3621           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3622           if(!weird) newVariant = VariantNormal;
3623           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3624           /* Get a move list just to see the header, which
3625              will tell us whether this is really bug or zh */
3626           if (ics_getting_history == H_FALSE) {
3627             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3628             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3629             SendToICS(str);
3630           }
3631     }
3632     
3633     /* Take action if this is the first board of a new game, or of a
3634        different game than is currently being displayed.  */
3635     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3636         relation == RELATION_ISOLATED_BOARD) {
3637
3638         /* Forget the old game and get the history (if any) of the new one */
3639         if (gameMode != BeginningOfGame) {
3640           Reset(TRUE, TRUE);
3641         }
3642         newGame = TRUE;
3643         if (appData.autoRaiseBoard) BoardToTop();
3644         prevMove = -3;
3645         if (gamenum == -1) {
3646             newGameMode = IcsIdle;
3647         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3648                    appData.getMoveList && !reqFlag) {
3649             /* Need to get game history */
3650             ics_getting_history = H_REQUESTED;
3651             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3652             SendToICS(str);
3653         }
3654
3655         /* Initially flip the board to have black on the bottom if playing
3656            black or if the ICS flip flag is set, but let the user change
3657            it with the Flip View button. */
3658         flipView = appData.autoFlipView ?
3659           (newGameMode == IcsPlayingBlack) || ics_flip :
3660           appData.flipView;
3661
3662         /* Done with values from previous mode; copy in new ones */
3663         gameMode = newGameMode;
3664         ModeHighlight();
3665         ics_gamenum = gamenum;
3666         if (gamenum == gs_gamenum) {
3667             int klen = strlen(gs_kind);
3668             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3669             sprintf(str, "ICS %s", gs_kind);
3670             gameInfo.event = StrSave(str);
3671         } else {
3672             gameInfo.event = StrSave("ICS game");
3673         }
3674         gameInfo.site = StrSave(appData.icsHost);
3675         gameInfo.date = PGNDate();
3676         gameInfo.round = StrSave("-");
3677         gameInfo.white = StrSave(white);
3678         gameInfo.black = StrSave(black);
3679         timeControl = basetime * 60 * 1000;
3680         timeControl_2 = 0;
3681         timeIncrement = increment * 1000;
3682         movesPerSession = 0;
3683         gameInfo.timeControl = TimeControlTagValue();
3684         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3685   if (appData.debugMode) {
3686     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3687     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3688     setbuf(debugFP, NULL);
3689   }
3690
3691         gameInfo.outOfBook = NULL;
3692
3693         /* Do we have the ratings? */
3694         if (strcmp(player1Name, white) == 0 &&
3695             strcmp(player2Name, black) == 0) {
3696             if (appData.debugMode)
3697               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3698                       player1Rating, player2Rating);
3699             gameInfo.whiteRating = player1Rating;
3700             gameInfo.blackRating = player2Rating;
3701         } else if (strcmp(player2Name, white) == 0 &&
3702                    strcmp(player1Name, black) == 0) {
3703             if (appData.debugMode)
3704               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3705                       player2Rating, player1Rating);
3706             gameInfo.whiteRating = player2Rating;
3707             gameInfo.blackRating = player1Rating;
3708         }
3709         player1Name[0] = player2Name[0] = NULLCHAR;
3710
3711         /* Silence shouts if requested */
3712         if (appData.quietPlay &&
3713             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3714             SendToICS(ics_prefix);
3715             SendToICS("set shout 0\n");
3716         }
3717     }
3718
3719     /* Deal with midgame name changes */
3720     if (!newGame) {
3721         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3722             if (gameInfo.white) free(gameInfo.white);
3723             gameInfo.white = StrSave(white);
3724         }
3725         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3726             if (gameInfo.black) free(gameInfo.black);
3727             gameInfo.black = StrSave(black);
3728         }
3729     }
3730
3731     /* Throw away game result if anything actually changes in examine mode */
3732     if (gameMode == IcsExamining && !newGame) {
3733         gameInfo.result = GameUnfinished;
3734         if (gameInfo.resultDetails != NULL) {
3735             free(gameInfo.resultDetails);
3736             gameInfo.resultDetails = NULL;
3737         }
3738     }
3739
3740     /* In pausing && IcsExamining mode, we ignore boards coming
3741        in if they are in a different variation than we are. */
3742     if (pauseExamInvalid) return;
3743     if (pausing && gameMode == IcsExamining) {
3744         if (moveNum <= pauseExamForwardMostMove) {
3745             pauseExamInvalid = TRUE;
3746             forwardMostMove = pauseExamForwardMostMove;
3747             return;
3748         }
3749     }
3750
3751   if (appData.debugMode) {
3752     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3753   }
3754     /* Parse the board */
3755     for (k = 0; k < ranks; k++) {
3756       for (j = 0; j < files; j++)
3757         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3758       if(gameInfo.holdingsWidth > 1) {
3759            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3760            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3761       }
3762     }
3763     CopyBoard(boards[moveNum], board);
3764     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3765     if (moveNum == 0) {
3766         startedFromSetupPosition =
3767           !CompareBoards(board, initialPosition);
3768         if(startedFromSetupPosition)
3769             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3770     }
3771
3772     /* [HGM] Set castling rights. Take the outermost Rooks,
3773        to make it also work for FRC opening positions. Note that board12
3774        is really defective for later FRC positions, as it has no way to
3775        indicate which Rook can castle if they are on the same side of King.
3776        For the initial position we grant rights to the outermost Rooks,
3777        and remember thos rights, and we then copy them on positions
3778        later in an FRC game. This means WB might not recognize castlings with
3779        Rooks that have moved back to their original position as illegal,
3780        but in ICS mode that is not its job anyway.
3781     */
3782     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3783     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3784
3785         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3786             if(board[0][i] == WhiteRook) j = i;
3787         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3788         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3789             if(board[0][i] == WhiteRook) j = i;
3790         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3791         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3792             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3793         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3794         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3795             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3796         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3797
3798         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3799         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3800             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3801         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3802             if(board[BOARD_HEIGHT-1][k] == bKing)
3803                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3804         if(gameInfo.variant == VariantTwoKings) {
3805             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3806             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3807             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3808         }
3809     } else { int r;
3810         r = boards[moveNum][CASTLING][0] = initialRights[0];
3811         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3812         r = boards[moveNum][CASTLING][1] = initialRights[1];
3813         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3814         r = boards[moveNum][CASTLING][3] = initialRights[3];
3815         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3816         r = boards[moveNum][CASTLING][4] = initialRights[4];
3817         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3818         /* wildcastle kludge: always assume King has rights */
3819         r = boards[moveNum][CASTLING][2] = initialRights[2];
3820         r = boards[moveNum][CASTLING][5] = initialRights[5];
3821     }
3822     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3823     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3824
3825
3826     if (ics_getting_history == H_GOT_REQ_HEADER ||
3827         ics_getting_history == H_GOT_UNREQ_HEADER) {
3828         /* This was an initial position from a move list, not
3829            the current position */
3830         return;
3831     }
3832
3833     /* Update currentMove and known move number limits */
3834     newMove = newGame || moveNum > forwardMostMove;
3835
3836     if (newGame) {
3837         forwardMostMove = backwardMostMove = currentMove = moveNum;
3838         if (gameMode == IcsExamining && moveNum == 0) {
3839           /* Workaround for ICS limitation: we are not told the wild
3840              type when starting to examine a game.  But if we ask for
3841              the move list, the move list header will tell us */
3842             ics_getting_history = H_REQUESTED;
3843             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3844             SendToICS(str);
3845         }
3846     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3847                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3848 #if ZIPPY
3849         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3850         /* [HGM] applied this also to an engine that is silently watching        */
3851         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3852             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3853             gameInfo.variant == currentlyInitializedVariant) {
3854           takeback = forwardMostMove - moveNum;
3855           for (i = 0; i < takeback; i++) {
3856             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3857             SendToProgram("undo\n", &first);
3858           }
3859         }
3860 #endif
3861
3862         forwardMostMove = moveNum;
3863         if (!pausing || currentMove > forwardMostMove)
3864           currentMove = forwardMostMove;
3865     } else {
3866         /* New part of history that is not contiguous with old part */
3867         if (pausing && gameMode == IcsExamining) {
3868             pauseExamInvalid = TRUE;
3869             forwardMostMove = pauseExamForwardMostMove;
3870             return;
3871         }
3872         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3873 #if ZIPPY
3874             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3875                 // [HGM] when we will receive the move list we now request, it will be
3876                 // fed to the engine from the first move on. So if the engine is not
3877                 // in the initial position now, bring it there.
3878                 InitChessProgram(&first, 0);
3879             }
3880 #endif
3881             ics_getting_history = H_REQUESTED;
3882             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3883             SendToICS(str);
3884         }
3885         forwardMostMove = backwardMostMove = currentMove = moveNum;
3886     }
3887
3888     /* Update the clocks */
3889     if (strchr(elapsed_time, '.')) {
3890       /* Time is in ms */
3891       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3892       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3893     } else {
3894       /* Time is in seconds */
3895       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3896       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3897     }
3898
3899
3900 #if ZIPPY
3901     if (appData.zippyPlay && newGame &&
3902         gameMode != IcsObserving && gameMode != IcsIdle &&
3903         gameMode != IcsExamining)
3904       ZippyFirstBoard(moveNum, basetime, increment);
3905 #endif
3906
3907     /* Put the move on the move list, first converting
3908        to canonical algebraic form. */
3909     if (moveNum > 0) {
3910   if (appData.debugMode) {
3911     if (appData.debugMode) { int f = forwardMostMove;
3912         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3913                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3914                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3915     }
3916     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3917     fprintf(debugFP, "moveNum = %d\n", moveNum);
3918     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3919     setbuf(debugFP, NULL);
3920   }
3921         if (moveNum <= backwardMostMove) {
3922             /* We don't know what the board looked like before
3923                this move.  Punt. */
3924             strcpy(parseList[moveNum - 1], move_str);
3925             strcat(parseList[moveNum - 1], " ");
3926             strcat(parseList[moveNum - 1], elapsed_time);
3927             moveList[moveNum - 1][0] = NULLCHAR;
3928         } else if (strcmp(move_str, "none") == 0) {
3929             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3930             /* Again, we don't know what the board looked like;
3931                this is really the start of the game. */
3932             parseList[moveNum - 1][0] = NULLCHAR;
3933             moveList[moveNum - 1][0] = NULLCHAR;
3934             backwardMostMove = moveNum;
3935             startedFromSetupPosition = TRUE;
3936             fromX = fromY = toX = toY = -1;
3937         } else {
3938           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3939           //                 So we parse the long-algebraic move string in stead of the SAN move
3940           int valid; char buf[MSG_SIZ], *prom;
3941
3942           // str looks something like "Q/a1-a2"; kill the slash
3943           if(str[1] == '/')
3944                 sprintf(buf, "%c%s", str[0], str+2);
3945           else  strcpy(buf, str); // might be castling
3946           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3947                 strcat(buf, prom); // long move lacks promo specification!
3948           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3949                 if(appData.debugMode)
3950                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3951                 strcpy(move_str, buf);
3952           }
3953           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3954                                 &fromX, &fromY, &toX, &toY, &promoChar)
3955                || ParseOneMove(buf, moveNum - 1, &moveType,
3956                                 &fromX, &fromY, &toX, &toY, &promoChar);
3957           // end of long SAN patch
3958           if (valid) {
3959             (void) CoordsToAlgebraic(boards[moveNum - 1],
3960                                      PosFlags(moveNum - 1),
3961                                      fromY, fromX, toY, toX, promoChar,
3962                                      parseList[moveNum-1]);
3963             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3964               case MT_NONE:
3965               case MT_STALEMATE:
3966               default:
3967                 break;
3968               case MT_CHECK:
3969                 if(gameInfo.variant != VariantShogi)
3970                     strcat(parseList[moveNum - 1], "+");
3971                 break;
3972               case MT_CHECKMATE:
3973               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3974                 strcat(parseList[moveNum - 1], "#");
3975                 break;
3976             }
3977             strcat(parseList[moveNum - 1], " ");
3978             strcat(parseList[moveNum - 1], elapsed_time);
3979             /* currentMoveString is set as a side-effect of ParseOneMove */
3980             strcpy(moveList[moveNum - 1], currentMoveString);
3981             strcat(moveList[moveNum - 1], "\n");
3982           } else {
3983             /* Move from ICS was illegal!?  Punt. */
3984   if (appData.debugMode) {
3985     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3986     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3987   }
3988             strcpy(parseList[moveNum - 1], move_str);
3989             strcat(parseList[moveNum - 1], " ");
3990             strcat(parseList[moveNum - 1], elapsed_time);
3991             moveList[moveNum - 1][0] = NULLCHAR;
3992             fromX = fromY = toX = toY = -1;
3993           }
3994         }
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3997     setbuf(debugFP, NULL);
3998   }
3999
4000 #if ZIPPY
4001         /* Send move to chess program (BEFORE animating it). */
4002         if (appData.zippyPlay && !newGame && newMove &&
4003            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4004
4005             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4006                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4007                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4008                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4009                             move_str);
4010                     DisplayError(str, 0);
4011                 } else {
4012                     if (first.sendTime) {
4013                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4014                     }
4015                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4016                     if (firstMove && !bookHit) {
4017                         firstMove = FALSE;
4018                         if (first.useColors) {
4019                           SendToProgram(gameMode == IcsPlayingWhite ?
4020                                         "white\ngo\n" :
4021                                         "black\ngo\n", &first);
4022                         } else {
4023                           SendToProgram("go\n", &first);
4024                         }
4025                         first.maybeThinking = TRUE;
4026                     }
4027                 }
4028             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4029               if (moveList[moveNum - 1][0] == NULLCHAR) {
4030                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4031                 DisplayError(str, 0);
4032               } else {
4033                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4034                 SendMoveToProgram(moveNum - 1, &first);
4035               }
4036             }
4037         }
4038 #endif
4039     }
4040
4041     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4042         /* If move comes from a remote source, animate it.  If it
4043            isn't remote, it will have already been animated. */
4044         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4045             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4046         }
4047         if (!pausing && appData.highlightLastMove) {
4048             SetHighlights(fromX, fromY, toX, toY);
4049         }
4050     }
4051
4052     /* Start the clocks */
4053     whiteFlag = blackFlag = FALSE;
4054     appData.clockMode = !(basetime == 0 && increment == 0);
4055     if (ticking == 0) {
4056       ics_clock_paused = TRUE;
4057       StopClocks();
4058     } else if (ticking == 1) {
4059       ics_clock_paused = FALSE;
4060     }
4061     if (gameMode == IcsIdle ||
4062         relation == RELATION_OBSERVING_STATIC ||
4063         relation == RELATION_EXAMINING ||
4064         ics_clock_paused)
4065       DisplayBothClocks();
4066     else
4067       StartClocks();
4068
4069     /* Display opponents and material strengths */
4070     if (gameInfo.variant != VariantBughouse &&
4071         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4072         if (tinyLayout || smallLayout) {
4073             if(gameInfo.variant == VariantNormal)
4074                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4075                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4076                     basetime, increment);
4077             else
4078                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4079                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4080                     basetime, increment, (int) gameInfo.variant);
4081         } else {
4082             if(gameInfo.variant == VariantNormal)
4083                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4084                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4085                     basetime, increment);
4086             else
4087                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4088                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4089                     basetime, increment, VariantName(gameInfo.variant));
4090         }
4091         DisplayTitle(str);
4092   if (appData.debugMode) {
4093     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4094   }
4095     }
4096
4097
4098     /* Display the board */
4099     if (!pausing && !appData.noGUI) {
4100       if (appData.premove)
4101           if (!gotPremove ||
4102              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4103              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4104               ClearPremoveHighlights();
4105
4106       DrawPosition(FALSE, boards[currentMove]);
4107       DisplayMove(moveNum - 1);
4108       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4109             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4110               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4111         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4112       }
4113     }
4114
4115     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4116 #if ZIPPY
4117     if(bookHit) { // [HGM] book: simulate book reply
4118         static char bookMove[MSG_SIZ]; // a bit generous?
4119
4120         programStats.nodes = programStats.depth = programStats.time =
4121         programStats.score = programStats.got_only_move = 0;
4122         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4123
4124         strcpy(bookMove, "move ");
4125         strcat(bookMove, bookHit);
4126         HandleMachineMove(bookMove, &first);
4127     }
4128 #endif
4129 }
4130
4131 void
4132 GetMoveListEvent()
4133 {
4134     char buf[MSG_SIZ];
4135     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4136         ics_getting_history = H_REQUESTED;
4137         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4138         SendToICS(buf);
4139     }
4140 }
4141
4142 void
4143 AnalysisPeriodicEvent(force)
4144      int force;
4145 {
4146     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4147          && !force) || !appData.periodicUpdates)
4148       return;
4149
4150     /* Send . command to Crafty to collect stats */
4151     SendToProgram(".\n", &first);
4152
4153     /* Don't send another until we get a response (this makes
4154        us stop sending to old Crafty's which don't understand
4155        the "." command (sending illegal cmds resets node count & time,
4156        which looks bad)) */
4157     programStats.ok_to_send = 0;
4158 }
4159
4160 void ics_update_width(new_width)
4161         int new_width;
4162 {
4163         ics_printf("set width %d\n", new_width);
4164 }
4165
4166 void
4167 SendMoveToProgram(moveNum, cps)
4168      int moveNum;
4169      ChessProgramState *cps;
4170 {
4171     char buf[MSG_SIZ];
4172
4173     if (cps->useUsermove) {
4174       SendToProgram("usermove ", cps);
4175     }
4176     if (cps->useSAN) {
4177       char *space;
4178       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4179         int len = space - parseList[moveNum];
4180         memcpy(buf, parseList[moveNum], len);
4181         buf[len++] = '\n';
4182         buf[len] = NULLCHAR;
4183       } else {
4184         sprintf(buf, "%s\n", parseList[moveNum]);
4185       }
4186       SendToProgram(buf, cps);
4187     } else {
4188       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4189         AlphaRank(moveList[moveNum], 4);
4190         SendToProgram(moveList[moveNum], cps);
4191         AlphaRank(moveList[moveNum], 4); // and back
4192       } else
4193       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4194        * the engine. It would be nice to have a better way to identify castle
4195        * moves here. */
4196       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4197                                                                          && cps->useOOCastle) {
4198         int fromX = moveList[moveNum][0] - AAA;
4199         int fromY = moveList[moveNum][1] - ONE;
4200         int toX = moveList[moveNum][2] - AAA;
4201         int toY = moveList[moveNum][3] - ONE;
4202         if((boards[moveNum][fromY][fromX] == WhiteKing
4203             && boards[moveNum][toY][toX] == WhiteRook)
4204            || (boards[moveNum][fromY][fromX] == BlackKing
4205                && boards[moveNum][toY][toX] == BlackRook)) {
4206           if(toX > fromX) SendToProgram("O-O\n", cps);
4207           else SendToProgram("O-O-O\n", cps);
4208         }
4209         else SendToProgram(moveList[moveNum], cps);
4210       }
4211       else SendToProgram(moveList[moveNum], cps);
4212       /* End of additions by Tord */
4213     }
4214
4215     /* [HGM] setting up the opening has brought engine in force mode! */
4216     /*       Send 'go' if we are in a mode where machine should play. */
4217     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4218         (gameMode == TwoMachinesPlay   ||
4219 #ifdef ZIPPY
4220          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4221 #endif
4222          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4223         SendToProgram("go\n", cps);
4224   if (appData.debugMode) {
4225     fprintf(debugFP, "(extra)\n");
4226   }
4227     }
4228     setboardSpoiledMachineBlack = 0;
4229 }
4230
4231 void
4232 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4233      ChessMove moveType;
4234      int fromX, fromY, toX, toY;
4235 {
4236     char user_move[MSG_SIZ];
4237
4238     switch (moveType) {
4239       default:
4240         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4241                 (int)moveType, fromX, fromY, toX, toY);
4242         DisplayError(user_move + strlen("say "), 0);
4243         break;
4244       case WhiteKingSideCastle:
4245       case BlackKingSideCastle:
4246       case WhiteQueenSideCastleWild:
4247       case BlackQueenSideCastleWild:
4248       /* PUSH Fabien */
4249       case WhiteHSideCastleFR:
4250       case BlackHSideCastleFR:
4251       /* POP Fabien */
4252         sprintf(user_move, "o-o\n");
4253         break;
4254       case WhiteQueenSideCastle:
4255       case BlackQueenSideCastle:
4256       case WhiteKingSideCastleWild:
4257       case BlackKingSideCastleWild:
4258       /* PUSH Fabien */
4259       case WhiteASideCastleFR:
4260       case BlackASideCastleFR:
4261       /* POP Fabien */
4262         sprintf(user_move, "o-o-o\n");
4263         break;
4264       case WhitePromotionQueen:
4265       case BlackPromotionQueen:
4266       case WhitePromotionRook:
4267       case BlackPromotionRook:
4268       case WhitePromotionBishop:
4269       case BlackPromotionBishop:
4270       case WhitePromotionKnight:
4271       case BlackPromotionKnight:
4272       case WhitePromotionKing:
4273       case BlackPromotionKing:
4274       case WhitePromotionChancellor:
4275       case BlackPromotionChancellor:
4276       case WhitePromotionArchbishop:
4277       case BlackPromotionArchbishop:
4278         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4279             sprintf(user_move, "%c%c%c%c=%c\n",
4280                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4281                 PieceToChar(WhiteFerz));
4282         else if(gameInfo.variant == VariantGreat)
4283             sprintf(user_move, "%c%c%c%c=%c\n",
4284                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4285                 PieceToChar(WhiteMan));
4286         else
4287             sprintf(user_move, "%c%c%c%c=%c\n",
4288                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4289                 PieceToChar(PromoPiece(moveType)));
4290         break;
4291       case WhiteDrop:
4292       case BlackDrop:
4293         sprintf(user_move, "%c@%c%c\n",
4294                 ToUpper(PieceToChar((ChessSquare) fromX)),
4295                 AAA + toX, ONE + toY);
4296         break;
4297       case NormalMove:
4298       case WhiteCapturesEnPassant:
4299       case BlackCapturesEnPassant:
4300       case IllegalMove:  /* could be a variant we don't quite understand */
4301         sprintf(user_move, "%c%c%c%c\n",
4302                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4303         break;
4304     }
4305     SendToICS(user_move);
4306     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4307         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4308 }
4309
4310 void
4311 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4312      int rf, ff, rt, ft;
4313      char promoChar;
4314      char move[7];
4315 {
4316     if (rf == DROP_RANK) {
4317         sprintf(move, "%c@%c%c\n",
4318                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4319     } else {
4320         if (promoChar == 'x' || promoChar == NULLCHAR) {
4321             sprintf(move, "%c%c%c%c\n",
4322                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4323         } else {
4324             sprintf(move, "%c%c%c%c%c\n",
4325                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4326         }
4327     }
4328 }
4329
4330 void
4331 ProcessICSInitScript(f)
4332      FILE *f;
4333 {
4334     char buf[MSG_SIZ];
4335
4336     while (fgets(buf, MSG_SIZ, f)) {
4337         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4338     }
4339
4340     fclose(f);
4341 }
4342
4343
4344 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4345 void
4346 AlphaRank(char *move, int n)
4347 {
4348 //    char *p = move, c; int x, y;
4349
4350     if (appData.debugMode) {
4351         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4352     }
4353
4354     if(move[1]=='*' &&
4355        move[2]>='0' && move[2]<='9' &&
4356        move[3]>='a' && move[3]<='x'    ) {
4357         move[1] = '@';
4358         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4359         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4360     } else
4361     if(move[0]>='0' && move[0]<='9' &&
4362        move[1]>='a' && move[1]<='x' &&
4363        move[2]>='0' && move[2]<='9' &&
4364        move[3]>='a' && move[3]<='x'    ) {
4365         /* input move, Shogi -> normal */
4366         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4367         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4368         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4369         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4370     } else
4371     if(move[1]=='@' &&
4372        move[3]>='0' && move[3]<='9' &&
4373        move[2]>='a' && move[2]<='x'    ) {
4374         move[1] = '*';
4375         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4376         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4377     } else
4378     if(
4379        move[0]>='a' && move[0]<='x' &&
4380        move[3]>='0' && move[3]<='9' &&
4381        move[2]>='a' && move[2]<='x'    ) {
4382          /* output move, normal -> Shogi */
4383         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4384         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4385         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4386         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4387         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4388     }
4389     if (appData.debugMode) {
4390         fprintf(debugFP, "   out = '%s'\n", move);
4391     }
4392 }
4393
4394 /* Parser for moves from gnuchess, ICS, or user typein box */
4395 Boolean
4396 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4397      char *move;
4398      int moveNum;
4399      ChessMove *moveType;
4400      int *fromX, *fromY, *toX, *toY;
4401      char *promoChar;
4402 {
4403     if (appData.debugMode) {
4404         fprintf(debugFP, "move to parse: %s\n", move);
4405     }
4406     *moveType = yylexstr(moveNum, move);
4407
4408     switch (*moveType) {
4409       case WhitePromotionChancellor:
4410       case BlackPromotionChancellor:
4411       case WhitePromotionArchbishop:
4412       case BlackPromotionArchbishop:
4413       case WhitePromotionQueen:
4414       case BlackPromotionQueen:
4415       case WhitePromotionRook:
4416       case BlackPromotionRook:
4417       case WhitePromotionBishop:
4418       case BlackPromotionBishop:
4419       case WhitePromotionKnight:
4420       case BlackPromotionKnight:
4421       case WhitePromotionKing:
4422       case BlackPromotionKing:
4423       case NormalMove:
4424       case WhiteCapturesEnPassant:
4425       case BlackCapturesEnPassant:
4426       case WhiteKingSideCastle:
4427       case WhiteQueenSideCastle:
4428       case BlackKingSideCastle:
4429       case BlackQueenSideCastle:
4430       case WhiteKingSideCastleWild:
4431       case WhiteQueenSideCastleWild:
4432       case BlackKingSideCastleWild:
4433       case BlackQueenSideCastleWild:
4434       /* Code added by Tord: */
4435       case WhiteHSideCastleFR:
4436       case WhiteASideCastleFR:
4437       case BlackHSideCastleFR:
4438       case BlackASideCastleFR:
4439       /* End of code added by Tord */
4440       case IllegalMove:         /* bug or odd chess variant */
4441         *fromX = currentMoveString[0] - AAA;
4442         *fromY = currentMoveString[1] - ONE;
4443         *toX = currentMoveString[2] - AAA;
4444         *toY = currentMoveString[3] - ONE;
4445         *promoChar = currentMoveString[4];
4446         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4447             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4448     if (appData.debugMode) {
4449         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4450     }
4451             *fromX = *fromY = *toX = *toY = 0;
4452             return FALSE;
4453         }
4454         if (appData.testLegality) {
4455           return (*moveType != IllegalMove);
4456         } else {
4457           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4458                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4459         }
4460
4461       case WhiteDrop:
4462       case BlackDrop:
4463         *fromX = *moveType == WhiteDrop ?
4464           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4465           (int) CharToPiece(ToLower(currentMoveString[0]));
4466         *fromY = DROP_RANK;
4467         *toX = currentMoveString[2] - AAA;
4468         *toY = currentMoveString[3] - ONE;
4469         *promoChar = NULLCHAR;
4470         return TRUE;
4471
4472       case AmbiguousMove:
4473       case ImpossibleMove:
4474       case (ChessMove) 0:       /* end of file */
4475       case ElapsedTime:
4476       case Comment:
4477       case PGNTag:
4478       case NAG:
4479       case WhiteWins:
4480       case BlackWins:
4481       case GameIsDrawn:
4482       default:
4483     if (appData.debugMode) {
4484         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4485     }
4486         /* bug? */
4487         *fromX = *fromY = *toX = *toY = 0;
4488         *promoChar = NULLCHAR;
4489         return FALSE;
4490     }
4491 }
4492
4493
4494 void
4495 ParsePV(char *pv)
4496 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4497   int fromX, fromY, toX, toY; char promoChar;
4498   ChessMove moveType;
4499   Boolean valid;
4500   int nr = 0;
4501
4502   endPV = forwardMostMove;
4503   do {
4504     while(*pv == ' ') pv++;
4505     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4506     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4507 if(appData.debugMode){
4508 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4509 }
4510     if(!valid && nr == 0 &&
4511        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4512         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4513     }
4514     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4515     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4516     nr++;
4517     if(endPV+1 > framePtr) break; // no space, truncate
4518     if(!valid) break;
4519     endPV++;
4520     CopyBoard(boards[endPV], boards[endPV-1]);
4521     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4522     moveList[endPV-1][0] = fromX + AAA;
4523     moveList[endPV-1][1] = fromY + ONE;
4524     moveList[endPV-1][2] = toX + AAA;
4525     moveList[endPV-1][3] = toY + ONE;
4526     parseList[endPV-1][0] = NULLCHAR;
4527   } while(valid);
4528   currentMove = endPV;
4529   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4530   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4531                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4532   DrawPosition(TRUE, boards[currentMove]);
4533 }
4534
4535 static int lastX, lastY;
4536
4537 Boolean
4538 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4539 {
4540         int startPV;
4541
4542         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4543         lastX = x; lastY = y;
4544         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4545         startPV = index;
4546       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4547       index = startPV;
4548         while(buf[index] && buf[index] != '\n') index++;
4549         buf[index] = 0;
4550         ParsePV(buf+startPV);
4551         *start = startPV; *end = index-1;
4552         return TRUE;
4553 }
4554
4555 Boolean
4556 LoadPV(int x, int y)
4557 { // called on right mouse click to load PV
4558   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4559   lastX = x; lastY = y;
4560   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4561   return TRUE;
4562 }
4563
4564 void
4565 UnLoadPV()
4566 {
4567   if(endPV < 0) return;
4568   endPV = -1;
4569   currentMove = forwardMostMove;
4570   ClearPremoveHighlights();
4571   DrawPosition(TRUE, boards[currentMove]);
4572 }
4573
4574 void
4575 MovePV(int x, int y, int h)
4576 { // step through PV based on mouse coordinates (called on mouse move)
4577   int margin = h>>3, step = 0;
4578
4579   if(endPV < 0) return;
4580   // we must somehow check if right button is still down (might be released off board!)
4581   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4582   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4583   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4584   if(!step) return;
4585   lastX = x; lastY = y;
4586   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4587   currentMove += step;
4588   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4589   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4590                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4591   DrawPosition(FALSE, boards[currentMove]);
4592 }
4593
4594
4595 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4596 // All positions will have equal probability, but the current method will not provide a unique
4597 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4598 #define DARK 1
4599 #define LITE 2
4600 #define ANY 3
4601
4602 int squaresLeft[4];
4603 int piecesLeft[(int)BlackPawn];
4604 int seed, nrOfShuffles;
4605
4606 void GetPositionNumber()
4607 {       // sets global variable seed
4608         int i;
4609
4610         seed = appData.defaultFrcPosition;
4611         if(seed < 0) { // randomize based on time for negative FRC position numbers
4612                 for(i=0; i<50; i++) seed += random();
4613                 seed = random() ^ random() >> 8 ^ random() << 8;
4614                 if(seed<0) seed = -seed;
4615         }
4616 }
4617
4618 int put(Board board, int pieceType, int rank, int n, int shade)
4619 // put the piece on the (n-1)-th empty squares of the given shade
4620 {
4621         int i;
4622
4623         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4624                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4625                         board[rank][i] = (ChessSquare) pieceType;
4626                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4627                         squaresLeft[ANY]--;
4628                         piecesLeft[pieceType]--;
4629                         return i;
4630                 }
4631         }
4632         return -1;
4633 }
4634
4635
4636 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4637 // calculate where the next piece goes, (any empty square), and put it there
4638 {
4639         int i;
4640
4641         i = seed % squaresLeft[shade];
4642         nrOfShuffles *= squaresLeft[shade];
4643         seed /= squaresLeft[shade];
4644         put(board, pieceType, rank, i, shade);
4645 }
4646
4647 void AddTwoPieces(Board board, int pieceType, int rank)
4648 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4649 {
4650         int i, n=squaresLeft[ANY], j=n-1, k;
4651
4652         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4653         i = seed % k;  // pick one
4654         nrOfShuffles *= k;
4655         seed /= k;
4656         while(i >= j) i -= j--;
4657         j = n - 1 - j; i += j;
4658         put(board, pieceType, rank, j, ANY);
4659         put(board, pieceType, rank, i, ANY);
4660 }
4661
4662 void SetUpShuffle(Board board, int number)
4663 {
4664         int i, p, first=1;
4665
4666         GetPositionNumber(); nrOfShuffles = 1;
4667
4668         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4669         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4670         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4671
4672         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4673
4674         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4675             p = (int) board[0][i];
4676             if(p < (int) BlackPawn) piecesLeft[p] ++;
4677             board[0][i] = EmptySquare;
4678         }
4679
4680         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4681             // shuffles restricted to allow normal castling put KRR first
4682             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4683                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4684             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4685                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4686             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4687                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4688             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4689                 put(board, WhiteRook, 0, 0, ANY);
4690             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4691         }
4692
4693         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4694             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4695             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4696                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4697                 while(piecesLeft[p] >= 2) {
4698                     AddOnePiece(board, p, 0, LITE);
4699                     AddOnePiece(board, p, 0, DARK);
4700                 }
4701                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4702             }
4703
4704         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4705             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4706             // but we leave King and Rooks for last, to possibly obey FRC restriction
4707             if(p == (int)WhiteRook) continue;
4708             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4709             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4710         }
4711
4712         // now everything is placed, except perhaps King (Unicorn) and Rooks
4713
4714         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4715             // Last King gets castling rights
4716             while(piecesLeft[(int)WhiteUnicorn]) {
4717                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4718                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4719             }
4720
4721             while(piecesLeft[(int)WhiteKing]) {
4722                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4723                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4724             }
4725
4726
4727         } else {
4728             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4729             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4730         }
4731
4732         // Only Rooks can be left; simply place them all
4733         while(piecesLeft[(int)WhiteRook]) {
4734                 i = put(board, WhiteRook, 0, 0, ANY);
4735                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4736                         if(first) {
4737                                 first=0;
4738                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4739                         }
4740                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4741                 }
4742         }
4743         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4744             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4745         }
4746
4747         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4748 }
4749
4750 int SetCharTable( char *table, const char * map )
4751 /* [HGM] moved here from winboard.c because of its general usefulness */
4752 /*       Basically a safe strcpy that uses the last character as King */
4753 {
4754     int result = FALSE; int NrPieces;
4755
4756     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4757                     && NrPieces >= 12 && !(NrPieces&1)) {
4758         int i; /* [HGM] Accept even length from 12 to 34 */
4759
4760         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4761         for( i=0; i<NrPieces/2-1; i++ ) {
4762             table[i] = map[i];
4763             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4764         }
4765         table[(int) WhiteKing]  = map[NrPieces/2-1];
4766         table[(int) BlackKing]  = map[NrPieces-1];
4767
4768         result = TRUE;
4769     }
4770
4771     return result;
4772 }
4773
4774 void Prelude(Board board)
4775 {       // [HGM] superchess: random selection of exo-pieces
4776         int i, j, k; ChessSquare p;
4777         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4778
4779         GetPositionNumber(); // use FRC position number
4780
4781         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4782             SetCharTable(pieceToChar, appData.pieceToCharTable);
4783             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4784                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4785         }
4786
4787         j = seed%4;                 seed /= 4;
4788         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4789         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4790         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4791         j = seed%3 + (seed%3 >= j); seed /= 3;
4792         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4793         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4794         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4795         j = seed%3;                 seed /= 3;
4796         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4797         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4798         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4799         j = seed%2 + (seed%2 >= j); seed /= 2;
4800         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4801         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4802         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4803         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4804         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4805         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4806         put(board, exoPieces[0],    0, 0, ANY);
4807         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4808 }
4809
4810 void
4811 InitPosition(redraw)
4812      int redraw;
4813 {
4814     ChessSquare (* pieces)[BOARD_FILES];
4815     int i, j, pawnRow, overrule,
4816     oldx = gameInfo.boardWidth,
4817     oldy = gameInfo.boardHeight,
4818     oldh = gameInfo.holdingsWidth,
4819     oldv = gameInfo.variant;
4820
4821     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4822
4823     /* [AS] Initialize pv info list [HGM] and game status */
4824     {
4825         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4826             pvInfoList[i].depth = 0;
4827             boards[i][EP_STATUS] = EP_NONE;
4828             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4829         }
4830
4831         initialRulePlies = 0; /* 50-move counter start */
4832
4833         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4834         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4835     }
4836
4837
4838     /* [HGM] logic here is completely changed. In stead of full positions */
4839     /* the initialized data only consist of the two backranks. The switch */
4840     /* selects which one we will use, which is than copied to the Board   */
4841     /* initialPosition, which for the rest is initialized by Pawns and    */
4842     /* empty squares. This initial position is then copied to boards[0],  */
4843     /* possibly after shuffling, so that it remains available.            */
4844
4845     gameInfo.holdingsWidth = 0; /* default board sizes */
4846     gameInfo.boardWidth    = 8;
4847     gameInfo.boardHeight   = 8;
4848     gameInfo.holdingsSize  = 0;
4849     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4850     for(i=0; i<BOARD_FILES-2; i++)
4851       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4852     initialPosition[EP_STATUS] = EP_NONE;
4853     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4854
4855     switch (gameInfo.variant) {
4856     case VariantFischeRandom:
4857       shuffleOpenings = TRUE;
4858     default:
4859       pieces = FIDEArray;
4860       break;
4861     case VariantShatranj:
4862       pieces = ShatranjArray;
4863       nrCastlingRights = 0;
4864       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4865       break;
4866     case VariantMakruk:
4867       pieces = makrukArray;
4868       nrCastlingRights = 0;
4869       startedFromSetupPosition = TRUE;
4870       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4871       break;
4872     case VariantTwoKings:
4873       pieces = twoKingsArray;
4874       break;
4875     case VariantCapaRandom:
4876       shuffleOpenings = TRUE;
4877     case VariantCapablanca:
4878       pieces = CapablancaArray;
4879       gameInfo.boardWidth = 10;
4880       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4881       break;
4882     case VariantGothic:
4883       pieces = GothicArray;
4884       gameInfo.boardWidth = 10;
4885       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4886       break;
4887     case VariantJanus:
4888       pieces = JanusArray;
4889       gameInfo.boardWidth = 10;
4890       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4891       nrCastlingRights = 6;
4892         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4893         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4894         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4895         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4896         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4897         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4898       break;
4899     case VariantFalcon:
4900       pieces = FalconArray;
4901       gameInfo.boardWidth = 10;
4902       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4903       break;
4904     case VariantXiangqi:
4905       pieces = XiangqiArray;
4906       gameInfo.boardWidth  = 9;
4907       gameInfo.boardHeight = 10;
4908       nrCastlingRights = 0;
4909       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4910       break;
4911     case VariantShogi:
4912       pieces = ShogiArray;
4913       gameInfo.boardWidth  = 9;
4914       gameInfo.boardHeight = 9;
4915       gameInfo.holdingsSize = 7;
4916       nrCastlingRights = 0;
4917       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4918       break;
4919     case VariantCourier:
4920       pieces = CourierArray;
4921       gameInfo.boardWidth  = 12;
4922       nrCastlingRights = 0;
4923       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4924       break;
4925     case VariantKnightmate:
4926       pieces = KnightmateArray;
4927       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4928       break;
4929     case VariantFairy:
4930       pieces = fairyArray;
4931       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4932       break;
4933     case VariantGreat:
4934       pieces = GreatArray;
4935       gameInfo.boardWidth = 10;
4936       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4937       gameInfo.holdingsSize = 8;
4938       break;
4939     case VariantSuper:
4940       pieces = FIDEArray;
4941       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4942       gameInfo.holdingsSize = 8;
4943       startedFromSetupPosition = TRUE;
4944       break;
4945     case VariantCrazyhouse:
4946     case VariantBughouse:
4947       pieces = FIDEArray;
4948       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4949       gameInfo.holdingsSize = 5;
4950       break;
4951     case VariantWildCastle:
4952       pieces = FIDEArray;
4953       /* !!?shuffle with kings guaranteed to be on d or e file */
4954       shuffleOpenings = 1;
4955       break;
4956     case VariantNoCastle:
4957       pieces = FIDEArray;
4958       nrCastlingRights = 0;
4959       /* !!?unconstrained back-rank shuffle */
4960       shuffleOpenings = 1;
4961       break;
4962     }
4963
4964     overrule = 0;
4965     if(appData.NrFiles >= 0) {
4966         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4967         gameInfo.boardWidth = appData.NrFiles;
4968     }
4969     if(appData.NrRanks >= 0) {
4970         gameInfo.boardHeight = appData.NrRanks;
4971     }
4972     if(appData.holdingsSize >= 0) {
4973         i = appData.holdingsSize;
4974         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4975         gameInfo.holdingsSize = i;
4976     }
4977     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4978     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4979         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4980
4981     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4982     if(pawnRow < 1) pawnRow = 1;
4983     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4984
4985     /* User pieceToChar list overrules defaults */
4986     if(appData.pieceToCharTable != NULL)
4987         SetCharTable(pieceToChar, appData.pieceToCharTable);
4988
4989     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4990
4991         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4992             s = (ChessSquare) 0; /* account holding counts in guard band */
4993         for( i=0; i<BOARD_HEIGHT; i++ )
4994             initialPosition[i][j] = s;
4995
4996         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4997         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4998         initialPosition[pawnRow][j] = WhitePawn;
4999         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5000         if(gameInfo.variant == VariantXiangqi) {
5001             if(j&1) {
5002                 initialPosition[pawnRow][j] =
5003                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5004                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5005                    initialPosition[2][j] = WhiteCannon;
5006                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5007                 }
5008             }
5009         }
5010         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5011     }
5012     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5013
5014             j=BOARD_LEFT+1;
5015             initialPosition[1][j] = WhiteBishop;
5016             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5017             j=BOARD_RGHT-2;
5018             initialPosition[1][j] = WhiteRook;
5019             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5020     }
5021
5022     if( nrCastlingRights == -1) {
5023         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5024         /*       This sets default castling rights from none to normal corners   */
5025         /* Variants with other castling rights must set them themselves above    */
5026         nrCastlingRights = 6;
5027         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5028         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5029         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5030         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5031         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5032         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5033      }
5034
5035      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5036      if(gameInfo.variant == VariantGreat) { // promotion commoners
5037         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5038         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5039         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5040         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5041      }
5042   if (appData.debugMode) {
5043     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5044   }
5045     if(shuffleOpenings) {
5046         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5047         startedFromSetupPosition = TRUE;
5048     }
5049     if(startedFromPositionFile) {
5050       /* [HGM] loadPos: use PositionFile for every new game */
5051       CopyBoard(initialPosition, filePosition);
5052       for(i=0; i<nrCastlingRights; i++)
5053           initialRights[i] = filePosition[CASTLING][i];
5054       startedFromSetupPosition = TRUE;
5055     }
5056
5057     CopyBoard(boards[0], initialPosition);
5058     if(oldx != gameInfo.boardWidth ||
5059        oldy != gameInfo.boardHeight ||
5060        oldh != gameInfo.holdingsWidth
5061 #ifdef GOTHIC
5062        || oldv == VariantGothic ||        // For licensing popups
5063        gameInfo.variant == VariantGothic
5064 #endif
5065 #ifdef FALCON
5066        || oldv == VariantFalcon ||
5067        gameInfo.variant == VariantFalcon
5068 #endif
5069                                          )
5070       {
5071             InitDrawingSizes(-2 ,0);
5072       }
5073
5074     if (redraw)
5075       DrawPosition(TRUE, boards[currentMove]);
5076
5077 }
5078
5079 void
5080 SendBoard(cps, moveNum)
5081      ChessProgramState *cps;
5082      int moveNum;
5083 {
5084     char message[MSG_SIZ];
5085
5086     if (cps->useSetboard) {
5087       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5088       sprintf(message, "setboard %s\n", fen);
5089       SendToProgram(message, cps);
5090       free(fen);
5091
5092     } else {
5093       ChessSquare *bp;
5094       int i, j;
5095       /* Kludge to set black to move, avoiding the troublesome and now
5096        * deprecated "black" command.
5097        */
5098       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5099
5100       SendToProgram("edit\n", cps);
5101       SendToProgram("#\n", cps);
5102       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5103         bp = &boards[moveNum][i][BOARD_LEFT];
5104         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5105           if ((int) *bp < (int) BlackPawn) {
5106             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5107                     AAA + j, ONE + i);
5108             if(message[0] == '+' || message[0] == '~') {
5109                 sprintf(message, "%c%c%c+\n",
5110                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5111                         AAA + j, ONE + i);
5112             }
5113             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5114                 message[1] = BOARD_RGHT   - 1 - j + '1';
5115                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5116             }
5117             SendToProgram(message, cps);
5118           }
5119         }
5120       }
5121
5122       SendToProgram("c\n", cps);
5123       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5124         bp = &boards[moveNum][i][BOARD_LEFT];
5125         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5126           if (((int) *bp != (int) EmptySquare)
5127               && ((int) *bp >= (int) BlackPawn)) {
5128             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5129                     AAA + j, ONE + i);
5130             if(message[0] == '+' || message[0] == '~') {
5131                 sprintf(message, "%c%c%c+\n",
5132                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5133                         AAA + j, ONE + i);
5134             }
5135             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5136                 message[1] = BOARD_RGHT   - 1 - j + '1';
5137                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5138             }
5139             SendToProgram(message, cps);
5140           }
5141         }
5142       }
5143
5144       SendToProgram(".\n", cps);
5145     }
5146     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5147 }
5148
5149 int
5150 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5151 {
5152     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5153     /* [HGM] add Shogi promotions */
5154     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5155     ChessSquare piece;
5156     ChessMove moveType;
5157     Boolean premove;
5158
5159     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5160     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5161
5162     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5163       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5164         return FALSE;
5165
5166     piece = boards[currentMove][fromY][fromX];
5167     if(gameInfo.variant == VariantShogi) {
5168         promotionZoneSize = 3;
5169         highestPromotingPiece = (int)WhiteFerz;
5170     } else if(gameInfo.variant == VariantMakruk) {
5171         promotionZoneSize = 3;
5172     }
5173
5174     // next weed out all moves that do not touch the promotion zone at all
5175     if((int)piece >= BlackPawn) {
5176         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5177              return FALSE;
5178         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5179     } else {
5180         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5181            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5182     }
5183
5184     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5185
5186     // weed out mandatory Shogi promotions
5187     if(gameInfo.variant == VariantShogi) {
5188         if(piece >= BlackPawn) {
5189             if(toY == 0 && piece == BlackPawn ||
5190                toY == 0 && piece == BlackQueen ||
5191                toY <= 1 && piece == BlackKnight) {
5192                 *promoChoice = '+';
5193                 return FALSE;
5194             }
5195         } else {
5196             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5197                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5198                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5199                 *promoChoice = '+';
5200                 return FALSE;
5201             }
5202         }
5203     }
5204
5205     // weed out obviously illegal Pawn moves
5206     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5207         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5208         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5209         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5210         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5211         // note we are not allowed to test for valid (non-)capture, due to premove
5212     }
5213
5214     // we either have a choice what to promote to, or (in Shogi) whether to promote
5215     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5216         *promoChoice = PieceToChar(BlackFerz);  // no choice
5217         return FALSE;
5218     }
5219     if(appData.alwaysPromoteToQueen) { // predetermined
5220         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5221              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5222         else *promoChoice = PieceToChar(BlackQueen);
5223         return FALSE;
5224     }
5225
5226     // suppress promotion popup on illegal moves that are not premoves
5227     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5228               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5229     if(appData.testLegality && !premove) {
5230         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5231                         fromY, fromX, toY, toX, NULLCHAR);
5232         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5233            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5234             return FALSE;
5235     }
5236
5237     return TRUE;
5238 }
5239
5240 int
5241 InPalace(row, column)
5242      int row, column;
5243 {   /* [HGM] for Xiangqi */
5244     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5245          column < (BOARD_WIDTH + 4)/2 &&
5246          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5247     return FALSE;
5248 }
5249
5250 int
5251 PieceForSquare (x, y)
5252      int x;
5253      int y;
5254 {
5255   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5256      return -1;
5257   else
5258      return boards[currentMove][y][x];
5259 }
5260
5261 int
5262 OKToStartUserMove(x, y)
5263      int x, y;
5264 {
5265     ChessSquare from_piece;
5266     int white_piece;
5267
5268     if (matchMode) return FALSE;
5269     if (gameMode == EditPosition) return TRUE;
5270
5271     if (x >= 0 && y >= 0)
5272       from_piece = boards[currentMove][y][x];
5273     else
5274       from_piece = EmptySquare;
5275
5276     if (from_piece == EmptySquare) return FALSE;
5277
5278     white_piece = (int)from_piece >= (int)WhitePawn &&
5279       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5280
5281     switch (gameMode) {
5282       case PlayFromGameFile:
5283       case AnalyzeFile:
5284       case TwoMachinesPlay:
5285       case EndOfGame:
5286         return FALSE;
5287
5288       case IcsObserving:
5289       case IcsIdle:
5290         return FALSE;
5291
5292       case MachinePlaysWhite:
5293       case IcsPlayingBlack:
5294         if (appData.zippyPlay) return FALSE;
5295         if (white_piece) {
5296             DisplayMoveError(_("You are playing Black"));
5297             return FALSE;
5298         }
5299         break;
5300
5301       case MachinePlaysBlack:
5302       case IcsPlayingWhite:
5303         if (appData.zippyPlay) return FALSE;
5304         if (!white_piece) {
5305             DisplayMoveError(_("You are playing White"));
5306             return FALSE;
5307         }
5308         break;
5309
5310       case EditGame:
5311         if (!white_piece && WhiteOnMove(currentMove)) {
5312             DisplayMoveError(_("It is White's turn"));
5313             return FALSE;
5314         }
5315         if (white_piece && !WhiteOnMove(currentMove)) {
5316             DisplayMoveError(_("It is Black's turn"));
5317             return FALSE;
5318         }
5319         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5320             /* Editing correspondence game history */
5321             /* Could disallow this or prompt for confirmation */
5322             cmailOldMove = -1;
5323         }
5324         break;
5325
5326       case BeginningOfGame:
5327         if (appData.icsActive) return FALSE;
5328         if (!appData.noChessProgram) {
5329             if (!white_piece) {
5330                 DisplayMoveError(_("You are playing White"));
5331                 return FALSE;
5332             }
5333         }
5334         break;
5335
5336       case Training:
5337         if (!white_piece && WhiteOnMove(currentMove)) {
5338             DisplayMoveError(_("It is White's turn"));
5339             return FALSE;
5340         }
5341         if (white_piece && !WhiteOnMove(currentMove)) {
5342             DisplayMoveError(_("It is Black's turn"));
5343             return FALSE;
5344         }
5345         break;
5346
5347       default:
5348       case IcsExamining:
5349         break;
5350     }
5351     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5352         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5353         && gameMode != AnalyzeFile && gameMode != Training) {
5354         DisplayMoveError(_("Displayed position is not current"));
5355         return FALSE;
5356     }
5357     return TRUE;
5358 }
5359
5360 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5361 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5362 int lastLoadGameUseList = FALSE;
5363 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5364 ChessMove lastLoadGameStart = (ChessMove) 0;
5365
5366 ChessMove
5367 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5368      int fromX, fromY, toX, toY;
5369      int promoChar;
5370      Boolean captureOwn;
5371 {
5372     ChessMove moveType;
5373     ChessSquare pdown, pup;
5374
5375     /* Check if the user is playing in turn.  This is complicated because we
5376        let the user "pick up" a piece before it is his turn.  So the piece he
5377        tried to pick up may have been captured by the time he puts it down!
5378        Therefore we use the color the user is supposed to be playing in this
5379        test, not the color of the piece that is currently on the starting
5380        square---except in EditGame mode, where the user is playing both
5381        sides; fortunately there the capture race can't happen.  (It can
5382        now happen in IcsExamining mode, but that's just too bad.  The user
5383        will get a somewhat confusing message in that case.)
5384        */
5385
5386     switch (gameMode) {
5387       case PlayFromGameFile:
5388       case AnalyzeFile:
5389       case TwoMachinesPlay:
5390       case EndOfGame:
5391       case IcsObserving:
5392       case IcsIdle:
5393         /* We switched into a game mode where moves are not accepted,
5394            perhaps while the mouse button was down. */
5395         return ImpossibleMove;
5396
5397       case MachinePlaysWhite:
5398         /* User is moving for Black */
5399         if (WhiteOnMove(currentMove)) {
5400             DisplayMoveError(_("It is White's turn"));
5401             return ImpossibleMove;
5402         }
5403         break;
5404
5405       case MachinePlaysBlack:
5406         /* User is moving for White */
5407         if (!WhiteOnMove(currentMove)) {
5408             DisplayMoveError(_("It is Black's turn"));
5409             return ImpossibleMove;
5410         }
5411         break;
5412
5413       case EditGame:
5414       case IcsExamining:
5415       case BeginningOfGame:
5416       case AnalyzeMode:
5417       case Training:
5418         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5419             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5420             /* User is moving for Black */
5421             if (WhiteOnMove(currentMove)) {
5422                 DisplayMoveError(_("It is White's turn"));
5423                 return ImpossibleMove;
5424             }
5425         } else {
5426             /* User is moving for White */
5427             if (!WhiteOnMove(currentMove)) {
5428                 DisplayMoveError(_("It is Black's turn"));
5429                 return ImpossibleMove;
5430             }
5431         }
5432         break;
5433
5434       case IcsPlayingBlack:
5435         /* User is moving for Black */
5436         if (WhiteOnMove(currentMove)) {
5437             if (!appData.premove) {
5438                 DisplayMoveError(_("It is White's turn"));
5439             } else if (toX >= 0 && toY >= 0) {
5440                 premoveToX = toX;
5441                 premoveToY = toY;
5442                 premoveFromX = fromX;
5443                 premoveFromY = fromY;
5444                 premovePromoChar = promoChar;
5445                 gotPremove = 1;
5446                 if (appData.debugMode)
5447                     fprintf(debugFP, "Got premove: fromX %d,"
5448                             "fromY %d, toX %d, toY %d\n",
5449                             fromX, fromY, toX, toY);
5450             }
5451             return ImpossibleMove;
5452         }
5453         break;
5454
5455       case IcsPlayingWhite:
5456         /* User is moving for White */
5457         if (!WhiteOnMove(currentMove)) {
5458             if (!appData.premove) {
5459                 DisplayMoveError(_("It is Black's turn"));
5460             } else if (toX >= 0 && toY >= 0) {
5461                 premoveToX = toX;
5462                 premoveToY = toY;
5463                 premoveFromX = fromX;
5464                 premoveFromY = fromY;
5465                 premovePromoChar = promoChar;
5466                 gotPremove = 1;
5467                 if (appData.debugMode)
5468                     fprintf(debugFP, "Got premove: fromX %d,"
5469                             "fromY %d, toX %d, toY %d\n",
5470                             fromX, fromY, toX, toY);
5471             }
5472             return ImpossibleMove;
5473         }
5474         break;
5475
5476       default:
5477         break;
5478
5479       case EditPosition:
5480         /* EditPosition, empty square, or different color piece;
5481            click-click move is possible */
5482         if (toX == -2 || toY == -2) {
5483             boards[0][fromY][fromX] = EmptySquare;
5484             return AmbiguousMove;
5485         } else if (toX >= 0 && toY >= 0) {
5486             boards[0][toY][toX] = boards[0][fromY][fromX];
5487             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5488                 if(boards[0][fromY][0] != EmptySquare) {
5489                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5490                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5491                 }
5492             } else
5493             if(fromX == BOARD_RGHT+1) {
5494                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5495                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5496                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5497                 }
5498             } else
5499             boards[0][fromY][fromX] = EmptySquare;
5500             return AmbiguousMove;
5501         }
5502         return ImpossibleMove;
5503     }
5504
5505     if(toX < 0 || toY < 0) return ImpossibleMove;
5506     pdown = boards[currentMove][fromY][fromX];
5507     pup = boards[currentMove][toY][toX];
5508
5509     /* [HGM] If move started in holdings, it means a drop */
5510     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5511          if( pup != EmptySquare ) return ImpossibleMove;
5512          if(appData.testLegality) {
5513              /* it would be more logical if LegalityTest() also figured out
5514               * which drops are legal. For now we forbid pawns on back rank.
5515               * Shogi is on its own here...
5516               */
5517              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5518                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5519                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5520          }
5521          return WhiteDrop; /* Not needed to specify white or black yet */
5522     }
5523
5524     userOfferedDraw = FALSE;
5525
5526     /* [HGM] always test for legality, to get promotion info */
5527     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5528                                          fromY, fromX, toY, toX, promoChar);
5529     /* [HGM] but possibly ignore an IllegalMove result */
5530     if (appData.testLegality) {
5531         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5532             DisplayMoveError(_("Illegal move"));
5533             return ImpossibleMove;
5534         }
5535     }
5536
5537     return moveType;
5538     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5539        function is made into one that returns an OK move type if FinishMove
5540        should be called. This to give the calling driver routine the
5541        opportunity to finish the userMove input with a promotion popup,
5542        without bothering the user with this for invalid or illegal moves */
5543
5544 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5545 }
5546
5547 /* Common tail of UserMoveEvent and DropMenuEvent */
5548 int
5549 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5550      ChessMove moveType;
5551      int fromX, fromY, toX, toY;
5552      /*char*/int promoChar;
5553 {
5554   char *bookHit = 0;
5555
5556   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5557     {
5558       // [HGM] superchess: suppress promotions to non-available piece
5559       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5560       if(WhiteOnMove(currentMove))
5561         {
5562           if(!boards[currentMove][k][BOARD_WIDTH-2])
5563             return 0;
5564         }
5565       else
5566         {
5567           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5568             return 0;
5569         }
5570     }
5571   
5572   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5573      move type in caller when we know the move is a legal promotion */
5574   if(moveType == NormalMove && promoChar)
5575     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5576   
5577   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5578      move type in caller when we know the move is a legal promotion */
5579   if(moveType == NormalMove && promoChar)
5580     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5581   
5582   /* [HGM] convert drag-and-drop piece drops to standard form */
5583   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5584     {
5585       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5586       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5587                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5588       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5589       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5590       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5591       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5592       fromY = DROP_RANK;
5593     }
5594   
5595   /* [HGM] <popupFix> The following if has been moved here from
5596      UserMoveEvent(). Because it seemed to belong here (why not allow
5597      piece drops in training games?), and because it can only be
5598      performed after it is known to what we promote. */
5599   if (gameMode == Training) 
5600     {
5601       /* compare the move played on the board to the next move in the
5602        * game. If they match, display the move and the opponent's response.
5603        * If they don't match, display an error message.
5604        */
5605       int saveAnimate;
5606       Board testBoard;
5607       CopyBoard(testBoard, boards[currentMove]);
5608       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5609
5610       if (CompareBoards(testBoard, boards[currentMove+1]))
5611         {
5612           ForwardInner(currentMove+1);
5613
5614           /* Autoplay the opponent's response.
5615            * if appData.animate was TRUE when Training mode was entered,
5616            * the response will be animated.
5617            */
5618           saveAnimate = appData.animate;
5619           appData.animate = animateTraining;
5620           ForwardInner(currentMove+1);
5621           appData.animate = saveAnimate;
5622
5623           /* check for the end of the game */
5624           if (currentMove >= forwardMostMove)
5625             {
5626               gameMode = PlayFromGameFile;
5627               ModeHighlight();
5628               SetTrainingModeOff();
5629               DisplayInformation(_("End of game"));
5630             }
5631         }
5632       else
5633         {
5634           DisplayError(_("Incorrect move"), 0);
5635         }
5636       return 1;
5637     }
5638
5639   /* Ok, now we know that the move is good, so we can kill
5640      the previous line in Analysis Mode */
5641   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5642                                 && currentMove < forwardMostMove) {
5643     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5644   }
5645
5646   /* If we need the chess program but it's dead, restart it */
5647   ResurrectChessProgram();
5648
5649   /* A user move restarts a paused game*/
5650   if (pausing)
5651     PauseEvent();
5652
5653   thinkOutput[0] = NULLCHAR;
5654
5655   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5656
5657   if (gameMode == BeginningOfGame)
5658     {
5659       if (appData.noChessProgram)
5660         {
5661           gameMode = EditGame;
5662           SetGameInfo();
5663         }
5664       else
5665         {
5666           char buf[MSG_SIZ];
5667           gameMode = MachinePlaysBlack;
5668           StartClocks();
5669           SetGameInfo();
5670           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5671           DisplayTitle(buf);
5672           if (first.sendName)
5673             {
5674               sprintf(buf, "name %s\n", gameInfo.white);
5675               SendToProgram(buf, &first);
5676             }
5677           StartClocks();
5678         }
5679       ModeHighlight();
5680     }
5681
5682   /* Relay move to ICS or chess engine */
5683   if (appData.icsActive)
5684     {
5685       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5686           gameMode == IcsExamining)
5687         {
5688           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5689           ics_user_moved = 1;
5690         }
5691     }
5692   else
5693     {
5694       if (first.sendTime && (gameMode == BeginningOfGame ||
5695                              gameMode == MachinePlaysWhite ||
5696                              gameMode == MachinePlaysBlack))
5697         {
5698           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5699         }
5700       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5701         {
5702           // [HGM] book: if program might be playing, let it use book
5703           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5704           first.maybeThinking = TRUE;
5705         }
5706       else
5707         SendMoveToProgram(forwardMostMove-1, &first);
5708       if (currentMove == cmailOldMove + 1)
5709         {
5710           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5711         }
5712     }
5713
5714   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5715
5716   switch (gameMode) 
5717     {
5718     case EditGame:
5719       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
5720         {
5721         case MT_NONE:
5722         case MT_CHECK:
5723           break;
5724         case MT_CHECKMATE:
5725         case MT_STAINMATE:
5726           if (WhiteOnMove(currentMove)) {
5727             GameEnds(BlackWins, "Black mates", GE_PLAYER);
5728           } else {
5729             GameEnds(WhiteWins, "White mates", GE_PLAYER);
5730           }
5731           break;
5732         case MT_STALEMATE:
5733           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5734           break;
5735         }
5736       break;
5737       
5738     case MachinePlaysBlack:
5739     case MachinePlaysWhite:
5740       /* disable certain menu options while machine is thinking */
5741       SetMachineThinkingEnables();
5742       break;
5743       
5744     default:
5745       break;
5746     }
5747   
5748   if(bookHit)
5749     { // [HGM] book: simulate book reply
5750       static char bookMove[MSG_SIZ]; // a bit generous?
5751
5752       programStats.nodes = programStats.depth = programStats.time =
5753         programStats.score = programStats.got_only_move = 0;
5754       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5755
5756       strcpy(bookMove, "move ");
5757       strcat(bookMove, bookHit);
5758       HandleMachineMove(bookMove, &first);
5759     }
5760
5761   return 1;
5762 }
5763
5764 void
5765 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5766      int fromX, fromY, toX, toY;
5767      int promoChar;
5768 {
5769     /* [HGM] This routine was added to allow calling of its two logical
5770        parts from other modules in the old way. Before, UserMoveEvent()
5771        automatically called FinishMove() if the move was OK, and returned
5772        otherwise. I separated the two, in order to make it possible to
5773        slip a promotion popup in between. But that it always needs two
5774        calls, to the first part, (now called UserMoveTest() ), and to
5775        FinishMove if the first part succeeded. Calls that do not need
5776        to do anything in between, can call this routine the old way.
5777     */
5778   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5779   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5780   if(moveType == AmbiguousMove)
5781     DrawPosition(FALSE, boards[currentMove]);
5782   else if(moveType != ImpossibleMove && moveType != Comment)
5783     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5784 }
5785
5786 void
5787 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5788      Board board;
5789      int flags;
5790      ChessMove kind;
5791      int rf, ff, rt, ft;
5792      VOIDSTAR closure;
5793 {
5794     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5795     Markers *m = (Markers *) closure;
5796     if(rf == fromY && ff == fromX)
5797         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5798                          || kind == WhiteCapturesEnPassant
5799                          || kind == BlackCapturesEnPassant);
5800     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5801 }
5802
5803 void
5804 MarkTargetSquares(int clear)
5805 {
5806   int x, y;
5807   if(!appData.markers || !appData.highlightDragging || 
5808      !appData.testLegality || gameMode == EditPosition) return;
5809   if(clear) {
5810     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5811   } else {
5812     int capt = 0;
5813     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5814     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5815       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5816       if(capt)
5817       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5818     }
5819   }
5820   DrawPosition(TRUE, NULL);
5821 }
5822
5823 void LeftClick(ClickType clickType, int xPix, int yPix)
5824 {
5825     int x, y;
5826     Boolean saveAnimate;
5827     static int second = 0, promotionChoice = 0;
5828     char promoChoice = NULLCHAR;
5829
5830     if (clickType == Press) ErrorPopDown();
5831     MarkTargetSquares(1);
5832
5833     x = EventToSquare(xPix, BOARD_WIDTH);
5834     y = EventToSquare(yPix, BOARD_HEIGHT);
5835     if (!flipView && y >= 0) {
5836         y = BOARD_HEIGHT - 1 - y;
5837     }
5838     if (flipView && x >= 0) {
5839         x = BOARD_WIDTH - 1 - x;
5840     }
5841
5842     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5843         if(clickType == Release) return; // ignore upclick of click-click destination
5844         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5845         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5846         if(gameInfo.holdingsWidth && 
5847                 (WhiteOnMove(currentMove) 
5848                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5849                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5850             // click in right holdings, for determining promotion piece
5851             ChessSquare p = boards[currentMove][y][x];
5852             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5853             if(p != EmptySquare) {
5854                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5855                 fromX = fromY = -1;
5856                 return;
5857             }
5858         }
5859         DrawPosition(FALSE, boards[currentMove]);
5860         return;
5861     }
5862
5863     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5864     if(clickType == Press
5865             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5866               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5867               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5868         return;
5869
5870     if (fromX == -1) {
5871         if (clickType == Press) {
5872             /* First square */
5873             if (OKToStartUserMove(x, y)) {
5874                 fromX = x;
5875                 fromY = y;
5876                 second = 0;
5877                 MarkTargetSquares(0);
5878                 DragPieceBegin(xPix, yPix);
5879                 if (appData.highlightDragging) {
5880                     SetHighlights(x, y, -1, -1);
5881                 }
5882             }
5883         }
5884         return;
5885     }
5886
5887     /* fromX != -1 */
5888     if (clickType == Press && gameMode != EditPosition) {
5889         ChessSquare fromP;
5890         ChessSquare toP;
5891         int frc;
5892
5893         // ignore off-board to clicks
5894         if(y < 0 || x < 0) return;
5895
5896         /* Check if clicking again on the same color piece */
5897         fromP = boards[currentMove][fromY][fromX];
5898         toP = boards[currentMove][y][x];
5899         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5900         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5901              WhitePawn <= toP && toP <= WhiteKing &&
5902              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5903              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5904             (BlackPawn <= fromP && fromP <= BlackKing && 
5905              BlackPawn <= toP && toP <= BlackKing &&
5906              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5907              !(fromP == BlackKing && toP == BlackRook && frc))) {
5908             /* Clicked again on same color piece -- changed his mind */
5909             second = (x == fromX && y == fromY);
5910             if (appData.highlightDragging) {
5911                 SetHighlights(x, y, -1, -1);
5912             } else {
5913                 ClearHighlights();
5914             }
5915             if (OKToStartUserMove(x, y)) {
5916                 fromX = x;
5917                 fromY = y;
5918                 MarkTargetSquares(0);
5919                 DragPieceBegin(xPix, yPix);
5920             }
5921             return;
5922         }
5923         // ignore clicks on holdings
5924         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5925     }
5926
5927     if (clickType == Release && x == fromX && y == fromY) {
5928         DragPieceEnd(xPix, yPix);
5929         if (appData.animateDragging) {
5930             /* Undo animation damage if any */
5931             DrawPosition(FALSE, NULL);
5932         }
5933         if (second) {
5934             /* Second up/down in same square; just abort move */
5935             second = 0;
5936             fromX = fromY = -1;
5937             ClearHighlights();
5938             gotPremove = 0;
5939             ClearPremoveHighlights();
5940         } else {
5941             /* First upclick in same square; start click-click mode */
5942             SetHighlights(x, y, -1, -1);
5943         }
5944         return;
5945     }
5946
5947     /* we now have a different from- and (possibly off-board) to-square */
5948     /* Completed move */
5949     toX = x;
5950     toY = y;
5951     saveAnimate = appData.animate;
5952     if (clickType == Press) {
5953         /* Finish clickclick move */
5954         if (appData.animate || appData.highlightLastMove) {
5955             SetHighlights(fromX, fromY, toX, toY);
5956         } else {
5957             ClearHighlights();
5958         }
5959     } else {
5960         /* Finish drag move */
5961         if (appData.highlightLastMove) {
5962             SetHighlights(fromX, fromY, toX, toY);
5963         } else {
5964             ClearHighlights();
5965         }
5966         DragPieceEnd(xPix, yPix);
5967         /* Don't animate move and drag both */
5968         appData.animate = FALSE;
5969     }
5970
5971     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5972     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5973         ChessSquare piece = boards[currentMove][fromY][fromX];
5974         if(gameMode == EditPosition && piece != EmptySquare &&
5975            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5976             int n;
5977              
5978             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5979                 n = PieceToNumber(piece - (int)BlackPawn);
5980                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5981                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5982                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5983             } else
5984             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5985                 n = PieceToNumber(piece);
5986                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5987                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5988                 boards[currentMove][n][BOARD_WIDTH-2]++;
5989             }
5990             boards[currentMove][fromY][fromX] = EmptySquare;
5991         }
5992         ClearHighlights();
5993         fromX = fromY = -1;
5994         DrawPosition(TRUE, boards[currentMove]);
5995         return;
5996     }
5997
5998     // off-board moves should not be highlighted
5999     if(x < 0 || x < 0) ClearHighlights();
6000
6001     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6002         SetHighlights(fromX, fromY, toX, toY);
6003         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6004             // [HGM] super: promotion to captured piece selected from holdings
6005             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6006             promotionChoice = TRUE;
6007             // kludge follows to temporarily execute move on display, without promoting yet
6008             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6009             boards[currentMove][toY][toX] = p;
6010             DrawPosition(FALSE, boards[currentMove]);
6011             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6012             boards[currentMove][toY][toX] = q;
6013             DisplayMessage("Click in holdings to choose piece", "");
6014             return;
6015         }
6016         PromotionPopUp();
6017     } else {
6018         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6019         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6020         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6021         fromX = fromY = -1;
6022     }
6023     appData.animate = saveAnimate;
6024     if (appData.animate || appData.animateDragging) {
6025         /* Undo animation damage if needed */
6026         DrawPosition(FALSE, NULL);
6027     }
6028 }
6029
6030 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6031 {
6032 //    char * hint = lastHint;
6033     FrontEndProgramStats stats;
6034
6035     stats.which = cps == &first ? 0 : 1;
6036     stats.depth = cpstats->depth;
6037     stats.nodes = cpstats->nodes;
6038     stats.score = cpstats->score;
6039     stats.time = cpstats->time;
6040     stats.pv = cpstats->movelist;
6041     stats.hint = lastHint;
6042     stats.an_move_index = 0;
6043     stats.an_move_count = 0;
6044
6045     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6046         stats.hint = cpstats->move_name;
6047         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6048         stats.an_move_count = cpstats->nr_moves;
6049     }
6050
6051     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6052
6053     SetProgramStats( &stats );
6054 }
6055
6056 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6057 {   // [HGM] book: this routine intercepts moves to simulate book replies
6058     char *bookHit = NULL;
6059
6060     //first determine if the incoming move brings opponent into his book
6061     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6062         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6063     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6064     if(bookHit != NULL && !cps->bookSuspend) {
6065         // make sure opponent is not going to reply after receiving move to book position
6066         SendToProgram("force\n", cps);
6067         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6068     }
6069     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6070     // now arrange restart after book miss
6071     if(bookHit) {
6072         // after a book hit we never send 'go', and the code after the call to this routine
6073         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6074         char buf[MSG_SIZ];
6075         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6076         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6077         SendToProgram(buf, cps);
6078         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6079     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6080         SendToProgram("go\n", cps);
6081         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6082     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6083         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6084             SendToProgram("go\n", cps);
6085         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6086     }
6087     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6088 }
6089
6090 char *savedMessage;
6091 ChessProgramState *savedState;
6092 void DeferredBookMove(void)
6093 {
6094         if(savedState->lastPing != savedState->lastPong)
6095                     ScheduleDelayedEvent(DeferredBookMove, 10);
6096         else
6097         HandleMachineMove(savedMessage, savedState);
6098 }
6099
6100 void
6101 HandleMachineMove(message, cps)
6102      char *message;
6103      ChessProgramState *cps;
6104 {
6105     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6106     char realname[MSG_SIZ];
6107     int fromX, fromY, toX, toY;
6108     ChessMove moveType;
6109     char promoChar;
6110     char *p;
6111     int machineWhite;
6112     char *bookHit;
6113
6114     cps->userError = 0;
6115
6116 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6117     /*
6118      * Kludge to ignore BEL characters
6119      */
6120     while (*message == '\007') message++;
6121
6122     /*
6123      * [HGM] engine debug message: ignore lines starting with '#' character
6124      */
6125     if(cps->debug && *message == '#') return;
6126
6127     /*
6128      * Look for book output
6129      */
6130     if (cps == &first && bookRequested) {
6131         if (message[0] == '\t' || message[0] == ' ') {
6132             /* Part of the book output is here; append it */
6133             strcat(bookOutput, message);
6134             strcat(bookOutput, "  \n");
6135             return;
6136         } else if (bookOutput[0] != NULLCHAR) {
6137             /* All of book output has arrived; display it */
6138             char *p = bookOutput;
6139             while (*p != NULLCHAR) {
6140                 if (*p == '\t') *p = ' ';
6141                 p++;
6142             }
6143             DisplayInformation(bookOutput);
6144             bookRequested = FALSE;
6145             /* Fall through to parse the current output */
6146         }
6147     }
6148
6149     /*
6150      * Look for machine move.
6151      */
6152     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6153         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6154     {
6155         /* This method is only useful on engines that support ping */
6156         if (cps->lastPing != cps->lastPong) {
6157           if (gameMode == BeginningOfGame) {
6158             /* Extra move from before last new; ignore */
6159             if (appData.debugMode) {
6160                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6161             }
6162           } else {
6163             if (appData.debugMode) {
6164                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6165                         cps->which, gameMode);
6166             }
6167
6168             SendToProgram("undo\n", cps);
6169           }
6170           return;
6171         }
6172
6173         switch (gameMode) {
6174           case BeginningOfGame:
6175             /* Extra move from before last reset; ignore */
6176             if (appData.debugMode) {
6177                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6178             }
6179             return;
6180
6181           case EndOfGame:
6182           case IcsIdle:
6183           default:
6184             /* Extra move after we tried to stop.  The mode test is
6185                not a reliable way of detecting this problem, but it's
6186                the best we can do on engines that don't support ping.
6187             */
6188             if (appData.debugMode) {
6189                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6190                         cps->which, gameMode);
6191             }
6192             SendToProgram("undo\n", cps);
6193             return;
6194
6195           case MachinePlaysWhite:
6196           case IcsPlayingWhite:
6197             machineWhite = TRUE;
6198             break;
6199
6200           case MachinePlaysBlack:
6201           case IcsPlayingBlack:
6202             machineWhite = FALSE;
6203             break;
6204
6205           case TwoMachinesPlay:
6206             machineWhite = (cps->twoMachinesColor[0] == 'w');
6207             break;
6208         }
6209         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6210             if (appData.debugMode) {
6211                 fprintf(debugFP,
6212                         "Ignoring move out of turn by %s, gameMode %d"
6213                         ", forwardMost %d\n",
6214                         cps->which, gameMode, forwardMostMove);
6215             }
6216             return;
6217         }
6218
6219     if (appData.debugMode) { int f = forwardMostMove;
6220         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6221                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6222                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6223     }
6224         if(cps->alphaRank) AlphaRank(machineMove, 4);
6225         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6226                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6227             /* Machine move could not be parsed; ignore it. */
6228             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6229                     machineMove, cps->which);
6230             DisplayError(buf1, 0);
6231             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6232                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6233             if (gameMode == TwoMachinesPlay) {
6234               GameEnds(machineWhite ? BlackWins : WhiteWins,
6235                        buf1, GE_XBOARD);
6236             }
6237             return;
6238         }
6239
6240         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6241         /* So we have to redo legality test with true e.p. status here,  */
6242         /* to make sure an illegal e.p. capture does not slip through,   */
6243         /* to cause a forfeit on a justified illegal-move complaint      */
6244         /* of the opponent.                                              */
6245         if( gameMode==TwoMachinesPlay && appData.testLegality
6246             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6247                                                               ) {
6248            ChessMove moveType;
6249            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6250                              fromY, fromX, toY, toX, promoChar);
6251             if (appData.debugMode) {
6252                 int i;
6253                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6254                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6255                 fprintf(debugFP, "castling rights\n");
6256             }
6257             if(moveType == IllegalMove) {
6258                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6259                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6260                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6261                            buf1, GE_XBOARD);
6262                 return;
6263            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6264            /* [HGM] Kludge to handle engines that send FRC-style castling
6265               when they shouldn't (like TSCP-Gothic) */
6266            switch(moveType) {
6267              case WhiteASideCastleFR:
6268              case BlackASideCastleFR:
6269                toX+=2;
6270                currentMoveString[2]++;
6271                break;
6272              case WhiteHSideCastleFR:
6273              case BlackHSideCastleFR:
6274                toX--;
6275                currentMoveString[2]--;
6276                break;
6277              default: ; // nothing to do, but suppresses warning of pedantic compilers
6278            }
6279         }
6280         hintRequested = FALSE;
6281         lastHint[0] = NULLCHAR;
6282         bookRequested = FALSE;
6283         /* Program may be pondering now */
6284         cps->maybeThinking = TRUE;
6285         if (cps->sendTime == 2) cps->sendTime = 1;
6286         if (cps->offeredDraw) cps->offeredDraw--;
6287
6288 #if ZIPPY
6289         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6290             first.initDone) {
6291           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6292           ics_user_moved = 1;
6293           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6294                 char buf[3*MSG_SIZ];
6295
6296                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6297                         programStats.score / 100.,
6298                         programStats.depth,
6299                         programStats.time / 100.,
6300                         (unsigned int)programStats.nodes,
6301                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6302                         programStats.movelist);
6303                 SendToICS(buf);
6304 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6305           }
6306         }
6307 #endif
6308         /* currentMoveString is set as a side-effect of ParseOneMove */
6309         strcpy(machineMove, currentMoveString);
6310         strcat(machineMove, "\n");
6311         strcpy(moveList[forwardMostMove], machineMove);
6312
6313         /* [AS] Save move info and clear stats for next move */
6314         pvInfoList[ forwardMostMove ].score = programStats.score;
6315         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6316         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6317         ClearProgramStats();
6318         thinkOutput[0] = NULLCHAR;
6319         hiddenThinkOutputState = 0;
6320
6321         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6322
6323         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6324         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6325             int count = 0;
6326
6327             while( count < adjudicateLossPlies ) {
6328                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6329
6330                 if( count & 1 ) {
6331                     score = -score; /* Flip score for winning side */
6332                 }
6333
6334                 if( score > adjudicateLossThreshold ) {
6335                     break;
6336                 }
6337
6338                 count++;
6339             }
6340
6341             if( count >= adjudicateLossPlies ) {
6342                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6343
6344                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6345                     "Xboard adjudication",
6346                     GE_XBOARD );
6347
6348                 return;
6349             }
6350         }
6351
6352         if( gameMode == TwoMachinesPlay ) {
6353           // [HGM] some adjudications useful with buggy engines
6354             int k, count = 0; static int bare = 1;
6355           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6356
6357
6358             if( appData.testLegality )
6359             {   /* [HGM] Some more adjudications for obstinate engines */
6360                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6361                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6362                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6363                 static int moveCount = 6;
6364                 ChessMove result;
6365                 char *reason = NULL;
6366
6367                 /* Count what is on board. */
6368                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6369                 {   ChessSquare p = boards[forwardMostMove][i][j];
6370                     int m=i;
6371
6372                     switch((int) p)
6373                     {   /* count B,N,R and other of each side */
6374                         case WhiteKing:
6375                         case BlackKing:
6376                              NrK++; break; // [HGM] atomic: count Kings
6377                         case WhiteKnight:
6378                              NrWN++; break;
6379                         case WhiteBishop:
6380                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6381                              bishopsColor |= 1 << ((i^j)&1);
6382                              NrWB++; break;
6383                         case BlackKnight:
6384                              NrBN++; break;
6385                         case BlackBishop:
6386                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6387                              bishopsColor |= 1 << ((i^j)&1);
6388                              NrBB++; break;
6389                         case WhiteRook:
6390                              NrWR++; break;
6391                         case BlackRook:
6392                              NrBR++; break;
6393                         case WhiteQueen:
6394                              NrWQ++; break;
6395                         case BlackQueen:
6396                              NrBQ++; break;
6397                         case EmptySquare:
6398                              break;
6399                         case BlackPawn:
6400                              m = 7-i;
6401                         case WhitePawn:
6402                              PawnAdvance += m; NrPawns++;
6403                     }
6404                     NrPieces += (p != EmptySquare);
6405                     NrW += ((int)p < (int)BlackPawn);
6406                     if(gameInfo.variant == VariantXiangqi &&
6407                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6408                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6409                         NrW -= ((int)p < (int)BlackPawn);
6410                     }
6411                 }
6412
6413                 /* Some material-based adjudications that have to be made before stalemate test */
6414                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6415                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6416                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6417                      if(appData.checkMates) {
6418                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6419                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6420                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6421                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6422                          return;
6423                      }
6424                 }
6425
6426                 /* Bare King in Shatranj (loses) or Losers (wins) */
6427                 if( NrW == 1 || NrPieces - NrW == 1) {
6428                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6429                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6430                      if(appData.checkMates) {
6431                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6432                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6433                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6434                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6435                          return;
6436                      }
6437                   } else
6438                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6439                   {    /* bare King */
6440                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6441                         if(appData.checkMates) {
6442                             /* but only adjudicate if adjudication enabled */
6443                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6444                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6445                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6446                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6447                             return;
6448                         }
6449                   }
6450                 } else bare = 1;
6451
6452
6453             // don't wait for engine to announce game end if we can judge ourselves
6454             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6455               case MT_CHECK:
6456                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6457                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6458                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6459                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6460                             checkCnt++;
6461                         if(checkCnt >= 2) {
6462                             reason = "Xboard adjudication: 3rd check";
6463                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6464                             break;
6465                         }
6466                     }
6467                 }
6468               case MT_NONE:
6469               default:
6470                 break;
6471               case MT_STALEMATE:
6472               case MT_STAINMATE:
6473                 reason = "Xboard adjudication: Stalemate";
6474                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6475                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6476                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6477                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6478                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6479                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6480                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6481                                                                         EP_CHECKMATE : EP_WINS);
6482                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6483                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6484                 }
6485                 break;
6486               case MT_CHECKMATE:
6487                 reason = "Xboard adjudication: Checkmate";
6488                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6489                 break;
6490             }
6491
6492                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6493                     case EP_STALEMATE:
6494                         result = GameIsDrawn; break;
6495                     case EP_CHECKMATE:
6496                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6497                     case EP_WINS:
6498                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6499                     default:
6500                         result = (ChessMove) 0;
6501                 }
6502                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6503                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6504                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6505                     GameEnds( result, reason, GE_XBOARD );
6506                     return;
6507                 }
6508
6509                 /* Next absolutely insufficient mating material. */
6510                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6511                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6512                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6513                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6514                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6515
6516                      /* always flag draws, for judging claims */
6517                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6518
6519                      if(appData.materialDraws) {
6520                          /* but only adjudicate them if adjudication enabled */
6521                          SendToProgram("force\n", cps->other); // suppress reply
6522                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6523                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6524                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6525                          return;
6526                      }
6527                 }
6528
6529                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6530                 if(NrPieces == 4 &&
6531                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6532                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6533                    || NrWN==2 || NrBN==2     /* KNNK */
6534                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6535                   ) ) {
6536                      if(--moveCount < 0 && appData.trivialDraws)
6537                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6538                           SendToProgram("force\n", cps->other); // suppress reply
6539                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6540                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6541                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6542                           return;
6543                      }
6544                 } else moveCount = 6;
6545             }
6546           }
6547           
6548           if (appData.debugMode) { int i;
6549             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6550                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6551                     appData.drawRepeats);
6552             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6553               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6554             
6555           }
6556
6557                 /* Check for rep-draws */
6558                 count = 0;
6559                 for(k = forwardMostMove-2;
6560                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6561                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6562                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6563                     k-=2)
6564                 {   int rights=0;
6565                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6566                         /* compare castling rights */
6567                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6568                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6569                                 rights++; /* King lost rights, while rook still had them */
6570                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6571                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6572                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6573                                    rights++; /* but at least one rook lost them */
6574                         }
6575                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6576                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6577                                 rights++; 
6578                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6579                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6580                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6581                                    rights++;
6582                         }
6583                         if( rights == 0 && ++count > appData.drawRepeats-2
6584                             && appData.drawRepeats > 1) {
6585                              /* adjudicate after user-specified nr of repeats */
6586                              SendToProgram("force\n", cps->other); // suppress reply
6587                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6588                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6589                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6590                                 // [HGM] xiangqi: check for forbidden perpetuals
6591                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6592                                 for(m=forwardMostMove; m>k; m-=2) {
6593                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6594                                         ourPerpetual = 0; // the current mover did not always check
6595                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6596                                         hisPerpetual = 0; // the opponent did not always check
6597                                 }
6598                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6599                                                                         ourPerpetual, hisPerpetual);
6600                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6601                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6602                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6603                                     return;
6604                                 }
6605                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6606                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6607                                 // Now check for perpetual chases
6608                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6609                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6610                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6611                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6612                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6613                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6614                                         return;
6615                                     }
6616                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6617                                         break; // Abort repetition-checking loop.
6618                                 }
6619                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6620                              }
6621                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6622                              return;
6623                         }
6624                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6625                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6626                     }
6627                 }
6628
6629                 /* Now we test for 50-move draws. Determine ply count */
6630                 count = forwardMostMove;
6631                 /* look for last irreversble move */
6632                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6633                     count--;
6634                 /* if we hit starting position, add initial plies */
6635                 if( count == backwardMostMove )
6636                     count -= initialRulePlies;
6637                 count = forwardMostMove - count;
6638                 if( count >= 100)
6639                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6640                          /* this is used to judge if draw claims are legal */
6641                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6642                          SendToProgram("force\n", cps->other); // suppress reply
6643                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6644                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6646                          return;
6647                 }
6648
6649                 /* if draw offer is pending, treat it as a draw claim
6650                  * when draw condition present, to allow engines a way to
6651                  * claim draws before making their move to avoid a race
6652                  * condition occurring after their move
6653                  */
6654                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6655                          char *p = NULL;
6656                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6657                              p = "Draw claim: 50-move rule";
6658                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6659                              p = "Draw claim: 3-fold repetition";
6660                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6661                              p = "Draw claim: insufficient mating material";
6662                          if( p != NULL ) {
6663                              SendToProgram("force\n", cps->other); // suppress reply
6664                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6665                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6666                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6667                              return;
6668                          }
6669                 }
6670
6671
6672                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6673                     SendToProgram("force\n", cps->other); // suppress reply
6674                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6675                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6676
6677                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6678
6679                     return;
6680                 }
6681         }
6682
6683         bookHit = NULL;
6684         if (gameMode == TwoMachinesPlay) {
6685             /* [HGM] relaying draw offers moved to after reception of move */
6686             /* and interpreting offer as claim if it brings draw condition */
6687             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6688                 SendToProgram("draw\n", cps->other);
6689             }
6690             if (cps->other->sendTime) {
6691                 SendTimeRemaining(cps->other,
6692                                   cps->other->twoMachinesColor[0] == 'w');
6693             }
6694             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6695             if (firstMove && !bookHit) {
6696                 firstMove = FALSE;
6697                 if (cps->other->useColors) {
6698                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6699                 }
6700                 SendToProgram("go\n", cps->other);
6701             }
6702             cps->other->maybeThinking = TRUE;
6703         }
6704
6705         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6706
6707         if (!pausing && appData.ringBellAfterMoves) {
6708             RingBell();
6709         }
6710
6711         /*
6712          * Reenable menu items that were disabled while
6713          * machine was thinking
6714          */
6715         if (gameMode != TwoMachinesPlay)
6716             SetUserThinkingEnables();
6717
6718         // [HGM] book: after book hit opponent has received move and is now in force mode
6719         // force the book reply into it, and then fake that it outputted this move by jumping
6720         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6721         if(bookHit) {
6722                 static char bookMove[MSG_SIZ]; // a bit generous?
6723
6724                 strcpy(bookMove, "move ");
6725                 strcat(bookMove, bookHit);
6726                 message = bookMove;
6727                 cps = cps->other;
6728                 programStats.nodes = programStats.depth = programStats.time =
6729                 programStats.score = programStats.got_only_move = 0;
6730                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6731
6732                 if(cps->lastPing != cps->lastPong) {
6733                     savedMessage = message; // args for deferred call
6734                     savedState = cps;
6735                     ScheduleDelayedEvent(DeferredBookMove, 10);
6736                     return;
6737                 }
6738                 goto FakeBookMove;
6739         }
6740
6741         return;
6742     }
6743
6744     /* Set special modes for chess engines.  Later something general
6745      *  could be added here; for now there is just one kludge feature,
6746      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6747      *  when "xboard" is given as an interactive command.
6748      */
6749     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6750         cps->useSigint = FALSE;
6751         cps->useSigterm = FALSE;
6752     }
6753     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6754       ParseFeatures(message+8, cps);
6755       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6756     }
6757
6758     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6759      * want this, I was asked to put it in, and obliged.
6760      */
6761     if (!strncmp(message, "setboard ", 9)) {
6762         Board initial_position;
6763
6764         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6765
6766         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6767             DisplayError(_("Bad FEN received from engine"), 0);
6768             return ;
6769         } else {
6770            Reset(TRUE, FALSE);
6771            CopyBoard(boards[0], initial_position);
6772            initialRulePlies = FENrulePlies;
6773            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6774            else gameMode = MachinePlaysBlack;
6775            DrawPosition(FALSE, boards[currentMove]);
6776         }
6777         return;
6778     }
6779
6780     /*
6781      * Look for communication commands
6782      */
6783     if (!strncmp(message, "telluser ", 9)) {
6784         DisplayNote(message + 9);
6785         return;
6786     }
6787     if (!strncmp(message, "tellusererror ", 14)) {
6788         cps->userError = 1;
6789         DisplayError(message + 14, 0);
6790         return;
6791     }
6792     if (!strncmp(message, "tellopponent ", 13)) {
6793       if (appData.icsActive) {
6794         if (loggedOn) {
6795           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6796           SendToICS(buf1);
6797         }
6798       } else {
6799         DisplayNote(message + 13);
6800       }
6801       return;
6802     }
6803     if (!strncmp(message, "tellothers ", 11)) {
6804       if (appData.icsActive) {
6805         if (loggedOn) {
6806           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6807           SendToICS(buf1);
6808         }
6809       }
6810       return;
6811     }
6812     if (!strncmp(message, "tellall ", 8)) {
6813       if (appData.icsActive) {
6814         if (loggedOn) {
6815           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6816           SendToICS(buf1);
6817         }
6818       } else {
6819         DisplayNote(message + 8);
6820       }
6821       return;
6822     }
6823     if (strncmp(message, "warning", 7) == 0) {
6824         /* Undocumented feature, use tellusererror in new code */
6825         DisplayError(message, 0);
6826         return;
6827     }
6828     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6829         strcpy(realname, cps->tidy);
6830         strcat(realname, " query");
6831         AskQuestion(realname, buf2, buf1, cps->pr);
6832         return;
6833     }
6834     /* Commands from the engine directly to ICS.  We don't allow these to be
6835      *  sent until we are logged on. Crafty kibitzes have been known to
6836      *  interfere with the login process.
6837      */
6838     if (loggedOn) {
6839         if (!strncmp(message, "tellics ", 8)) {
6840             SendToICS(message + 8);
6841             SendToICS("\n");
6842             return;
6843         }
6844         if (!strncmp(message, "tellicsnoalias ", 15)) {
6845             SendToICS(ics_prefix);
6846             SendToICS(message + 15);
6847             SendToICS("\n");
6848             return;
6849         }
6850         /* The following are for backward compatibility only */
6851         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6852             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6853             SendToICS(ics_prefix);
6854             SendToICS(message);
6855             SendToICS("\n");
6856             return;
6857         }
6858     }
6859     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6860         return;
6861     }
6862     /*
6863      * If the move is illegal, cancel it and redraw the board.
6864      * Also deal with other error cases.  Matching is rather loose
6865      * here to accommodate engines written before the spec.
6866      */
6867     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6868         strncmp(message, "Error", 5) == 0) {
6869         if (StrStr(message, "name") ||
6870             StrStr(message, "rating") || StrStr(message, "?") ||
6871             StrStr(message, "result") || StrStr(message, "board") ||
6872             StrStr(message, "bk") || StrStr(message, "computer") ||
6873             StrStr(message, "variant") || StrStr(message, "hint") ||
6874             StrStr(message, "random") || StrStr(message, "depth") ||
6875             StrStr(message, "accepted")) {
6876             return;
6877         }
6878         if (StrStr(message, "protover")) {
6879           /* Program is responding to input, so it's apparently done
6880              initializing, and this error message indicates it is
6881              protocol version 1.  So we don't need to wait any longer
6882              for it to initialize and send feature commands. */
6883           FeatureDone(cps, 1);
6884           cps->protocolVersion = 1;
6885           return;
6886         }
6887         cps->maybeThinking = FALSE;
6888
6889         if (StrStr(message, "draw")) {
6890             /* Program doesn't have "draw" command */
6891             cps->sendDrawOffers = 0;
6892             return;
6893         }
6894         if (cps->sendTime != 1 &&
6895             (StrStr(message, "time") || StrStr(message, "otim"))) {
6896           /* Program apparently doesn't have "time" or "otim" command */
6897           cps->sendTime = 0;
6898           return;
6899         }
6900         if (StrStr(message, "analyze")) {
6901             cps->analysisSupport = FALSE;
6902             cps->analyzing = FALSE;
6903             Reset(FALSE, TRUE);
6904             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6905             DisplayError(buf2, 0);
6906             return;
6907         }
6908         if (StrStr(message, "(no matching move)st")) {
6909           /* Special kludge for GNU Chess 4 only */
6910           cps->stKludge = TRUE;
6911           SendTimeControl(cps, movesPerSession, timeControl,
6912                           timeIncrement, appData.searchDepth,
6913                           searchTime);
6914           return;
6915         }
6916         if (StrStr(message, "(no matching move)sd")) {
6917           /* Special kludge for GNU Chess 4 only */
6918           cps->sdKludge = TRUE;
6919           SendTimeControl(cps, movesPerSession, timeControl,
6920                           timeIncrement, appData.searchDepth,
6921                           searchTime);
6922           return;
6923         }
6924         if (!StrStr(message, "llegal")) {
6925             return;
6926         }
6927         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6928             gameMode == IcsIdle) return;
6929         if (forwardMostMove <= backwardMostMove) return;
6930         if (pausing) PauseEvent();
6931       if(appData.forceIllegal) {
6932             // [HGM] illegal: machine refused move; force position after move into it
6933           SendToProgram("force\n", cps);
6934           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6935                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6936                 // when black is to move, while there might be nothing on a2 or black
6937                 // might already have the move. So send the board as if white has the move.
6938                 // But first we must change the stm of the engine, as it refused the last move
6939                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6940                 if(WhiteOnMove(forwardMostMove)) {
6941                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6942                     SendBoard(cps, forwardMostMove); // kludgeless board
6943                 } else {
6944                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6945                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6946                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6947                 }
6948           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6949             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6950                  gameMode == TwoMachinesPlay)
6951               SendToProgram("go\n", cps);
6952             return;
6953       } else
6954         if (gameMode == PlayFromGameFile) {
6955             /* Stop reading this game file */
6956             gameMode = EditGame;
6957             ModeHighlight();
6958         }
6959         currentMove = --forwardMostMove;
6960         DisplayMove(currentMove-1); /* before DisplayMoveError */
6961         SwitchClocks();
6962         DisplayBothClocks();
6963         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6964                 parseList[currentMove], cps->which);
6965         DisplayMoveError(buf1);
6966         DrawPosition(FALSE, boards[currentMove]);
6967
6968         /* [HGM] illegal-move claim should forfeit game when Xboard */
6969         /* only passes fully legal moves                            */
6970         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6971             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6972                                 "False illegal-move claim", GE_XBOARD );
6973         }
6974         return;
6975     }
6976     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6977         /* Program has a broken "time" command that
6978            outputs a string not ending in newline.
6979            Don't use it. */
6980         cps->sendTime = 0;
6981     }
6982
6983     /*
6984      * If chess program startup fails, exit with an error message.
6985      * Attempts to recover here are futile.
6986      */
6987     if ((StrStr(message, "unknown host") != NULL)
6988         || (StrStr(message, "No remote directory") != NULL)
6989         || (StrStr(message, "not found") != NULL)
6990         || (StrStr(message, "No such file") != NULL)
6991         || (StrStr(message, "can't alloc") != NULL)
6992         || (StrStr(message, "Permission denied") != NULL)) {
6993
6994         cps->maybeThinking = FALSE;
6995         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6996                 cps->which, cps->program, cps->host, message);
6997         RemoveInputSource(cps->isr);
6998         DisplayFatalError(buf1, 0, 1);
6999         return;
7000     }
7001
7002     /*
7003      * Look for hint output
7004      */
7005     if (sscanf(message, "Hint: %s", buf1) == 1) {
7006         if (cps == &first && hintRequested) {
7007             hintRequested = FALSE;
7008             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7009                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7010                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7011                                     PosFlags(forwardMostMove),
7012                                     fromY, fromX, toY, toX, promoChar, buf1);
7013                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7014                 DisplayInformation(buf2);
7015             } else {
7016                 /* Hint move could not be parsed!? */
7017               snprintf(buf2, sizeof(buf2),
7018                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7019                         buf1, cps->which);
7020                 DisplayError(buf2, 0);
7021             }
7022         } else {
7023             strcpy(lastHint, buf1);
7024         }
7025         return;
7026     }
7027
7028     /*
7029      * Ignore other messages if game is not in progress
7030      */
7031     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7032         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7033
7034     /*
7035      * look for win, lose, draw, or draw offer
7036      */
7037     if (strncmp(message, "1-0", 3) == 0) {
7038         char *p, *q, *r = "";
7039         p = strchr(message, '{');
7040         if (p) {
7041             q = strchr(p, '}');
7042             if (q) {
7043                 *q = NULLCHAR;
7044                 r = p + 1;
7045             }
7046         }
7047         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7048         return;
7049     } else if (strncmp(message, "0-1", 3) == 0) {
7050         char *p, *q, *r = "";
7051         p = strchr(message, '{');
7052         if (p) {
7053             q = strchr(p, '}');
7054             if (q) {
7055                 *q = NULLCHAR;
7056                 r = p + 1;
7057             }
7058         }
7059         /* Kludge for Arasan 4.1 bug */
7060         if (strcmp(r, "Black resigns") == 0) {
7061             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7062             return;
7063         }
7064         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7065         return;
7066     } else if (strncmp(message, "1/2", 3) == 0) {
7067         char *p, *q, *r = "";
7068         p = strchr(message, '{');
7069         if (p) {
7070             q = strchr(p, '}');
7071             if (q) {
7072                 *q = NULLCHAR;
7073                 r = p + 1;
7074             }
7075         }
7076
7077         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7078         return;
7079
7080     } else if (strncmp(message, "White resign", 12) == 0) {
7081         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7082         return;
7083     } else if (strncmp(message, "Black resign", 12) == 0) {
7084         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7085         return;
7086     } else if (strncmp(message, "White matches", 13) == 0 ||
7087                strncmp(message, "Black matches", 13) == 0   ) {
7088         /* [HGM] ignore GNUShogi noises */
7089         return;
7090     } else if (strncmp(message, "White", 5) == 0 &&
7091                message[5] != '(' &&
7092                StrStr(message, "Black") == NULL) {
7093         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7094         return;
7095     } else if (strncmp(message, "Black", 5) == 0 &&
7096                message[5] != '(') {
7097         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7098         return;
7099     } else if (strcmp(message, "resign") == 0 ||
7100                strcmp(message, "computer resigns") == 0) {
7101         switch (gameMode) {
7102           case MachinePlaysBlack:
7103           case IcsPlayingBlack:
7104             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7105             break;
7106           case MachinePlaysWhite:
7107           case IcsPlayingWhite:
7108             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7109             break;
7110           case TwoMachinesPlay:
7111             if (cps->twoMachinesColor[0] == 'w')
7112               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7113             else
7114               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7115             break;
7116           default:
7117             /* can't happen */
7118             break;
7119         }
7120         return;
7121     } else if (strncmp(message, "opponent mates", 14) == 0) {
7122         switch (gameMode) {
7123           case MachinePlaysBlack:
7124           case IcsPlayingBlack:
7125             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7126             break;
7127           case MachinePlaysWhite:
7128           case IcsPlayingWhite:
7129             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7130             break;
7131           case TwoMachinesPlay:
7132             if (cps->twoMachinesColor[0] == 'w')
7133               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7134             else
7135               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7136             break;
7137           default:
7138             /* can't happen */
7139             break;
7140         }
7141         return;
7142     } else if (strncmp(message, "computer mates", 14) == 0) {
7143         switch (gameMode) {
7144           case MachinePlaysBlack:
7145           case IcsPlayingBlack:
7146             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7147             break;
7148           case MachinePlaysWhite:
7149           case IcsPlayingWhite:
7150             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7151             break;
7152           case TwoMachinesPlay:
7153             if (cps->twoMachinesColor[0] == 'w')
7154               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7155             else
7156               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7157             break;
7158           default:
7159             /* can't happen */
7160             break;
7161         }
7162         return;
7163     } else if (strncmp(message, "checkmate", 9) == 0) {
7164         if (WhiteOnMove(forwardMostMove)) {
7165             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7166         } else {
7167             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7168         }
7169         return;
7170     } else if (strstr(message, "Draw") != NULL ||
7171                strstr(message, "game is a draw") != NULL) {
7172         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7173         return;
7174     } else if (strstr(message, "offer") != NULL &&
7175                strstr(message, "draw") != NULL) {
7176 #if ZIPPY
7177         if (appData.zippyPlay && first.initDone) {
7178             /* Relay offer to ICS */
7179             SendToICS(ics_prefix);
7180             SendToICS("draw\n");
7181         }
7182 #endif
7183         cps->offeredDraw = 2; /* valid until this engine moves twice */
7184         if (gameMode == TwoMachinesPlay) {
7185             if (cps->other->offeredDraw) {
7186                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7187             /* [HGM] in two-machine mode we delay relaying draw offer      */
7188             /* until after we also have move, to see if it is really claim */
7189             }
7190         } else if (gameMode == MachinePlaysWhite ||
7191                    gameMode == MachinePlaysBlack) {
7192           if (userOfferedDraw) {
7193             DisplayInformation(_("Machine accepts your draw offer"));
7194             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7195           } else {
7196             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7197           }
7198         }
7199     }
7200
7201
7202     /*
7203      * Look for thinking output
7204      */
7205     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7206           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7207                                 ) {
7208         int plylev, mvleft, mvtot, curscore, time;
7209         char mvname[MOVE_LEN];
7210         u64 nodes; // [DM]
7211         char plyext;
7212         int ignore = FALSE;
7213         int prefixHint = FALSE;
7214         mvname[0] = NULLCHAR;
7215
7216         switch (gameMode) {
7217           case MachinePlaysBlack:
7218           case IcsPlayingBlack:
7219             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7220             break;
7221           case MachinePlaysWhite:
7222           case IcsPlayingWhite:
7223             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7224             break;
7225           case AnalyzeMode:
7226           case AnalyzeFile:
7227             break;
7228           case IcsObserving: /* [DM] icsEngineAnalyze */
7229             if (!appData.icsEngineAnalyze) ignore = TRUE;
7230             break;
7231           case TwoMachinesPlay:
7232             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7233                 ignore = TRUE;
7234             }
7235             break;
7236           default:
7237             ignore = TRUE;
7238             break;
7239         }
7240
7241         if (!ignore) {
7242             buf1[0] = NULLCHAR;
7243             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7244                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7245
7246                 if (plyext != ' ' && plyext != '\t') {
7247                     time *= 100;
7248                 }
7249
7250                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7251                 if( cps->scoreIsAbsolute && 
7252                     ( gameMode == MachinePlaysBlack ||
7253                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7254                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7255                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7256                      !WhiteOnMove(currentMove)
7257                     ) )
7258                 {
7259                     curscore = -curscore;
7260                 }
7261
7262
7263                 programStats.depth = plylev;
7264                 programStats.nodes = nodes;
7265                 programStats.time = time;
7266                 programStats.score = curscore;
7267                 programStats.got_only_move = 0;
7268
7269                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7270                         int ticklen;
7271
7272                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7273                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7274                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7275                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7276                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7277                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7278                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7279                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7280                 }
7281
7282                 /* Buffer overflow protection */
7283                 if (buf1[0] != NULLCHAR) {
7284                     if (strlen(buf1) >= sizeof(programStats.movelist)
7285                         && appData.debugMode) {
7286                         fprintf(debugFP,
7287                                 "PV is too long; using the first %u bytes.\n",
7288                                 (unsigned) sizeof(programStats.movelist) - 1);
7289                     }
7290
7291                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7292                 } else {
7293                     sprintf(programStats.movelist, " no PV\n");
7294                 }
7295
7296                 if (programStats.seen_stat) {
7297                     programStats.ok_to_send = 1;
7298                 }
7299
7300                 if (strchr(programStats.movelist, '(') != NULL) {
7301                     programStats.line_is_book = 1;
7302                     programStats.nr_moves = 0;
7303                     programStats.moves_left = 0;
7304                 } else {
7305                     programStats.line_is_book = 0;
7306                 }
7307
7308                 SendProgramStatsToFrontend( cps, &programStats );
7309
7310                 /*
7311                     [AS] Protect the thinkOutput buffer from overflow... this
7312                     is only useful if buf1 hasn't overflowed first!
7313                 */
7314                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7315                         plylev,
7316                         (gameMode == TwoMachinesPlay ?
7317                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7318                         ((double) curscore) / 100.0,
7319                         prefixHint ? lastHint : "",
7320                         prefixHint ? " " : "" );
7321
7322                 if( buf1[0] != NULLCHAR ) {
7323                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7324
7325                     if( strlen(buf1) > max_len ) {
7326                         if( appData.debugMode) {
7327                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7328                         }
7329                         buf1[max_len+1] = '\0';
7330                     }
7331
7332                     strcat( thinkOutput, buf1 );
7333                 }
7334
7335                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7336                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7337                     DisplayMove(currentMove - 1);
7338                 }
7339                 return;
7340
7341             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7342                 /* crafty (9.25+) says "(only move) <move>"
7343                  * if there is only 1 legal move
7344                  */
7345                 sscanf(p, "(only move) %s", buf1);
7346                 sprintf(thinkOutput, "%s (only move)", buf1);
7347                 sprintf(programStats.movelist, "%s (only move)", buf1);
7348                 programStats.depth = 1;
7349                 programStats.nr_moves = 1;
7350                 programStats.moves_left = 1;
7351                 programStats.nodes = 1;
7352                 programStats.time = 1;
7353                 programStats.got_only_move = 1;
7354
7355                 /* Not really, but we also use this member to
7356                    mean "line isn't going to change" (Crafty
7357                    isn't searching, so stats won't change) */
7358                 programStats.line_is_book = 1;
7359
7360                 SendProgramStatsToFrontend( cps, &programStats );
7361
7362                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7363                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7364                     DisplayMove(currentMove - 1);
7365                 }
7366                 return;
7367             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7368                               &time, &nodes, &plylev, &mvleft,
7369                               &mvtot, mvname) >= 5) {
7370                 /* The stat01: line is from Crafty (9.29+) in response
7371                    to the "." command */
7372                 programStats.seen_stat = 1;
7373                 cps->maybeThinking = TRUE;
7374
7375                 if (programStats.got_only_move || !appData.periodicUpdates)
7376                   return;
7377
7378                 programStats.depth = plylev;
7379                 programStats.time = time;
7380                 programStats.nodes = nodes;
7381                 programStats.moves_left = mvleft;
7382                 programStats.nr_moves = mvtot;
7383                 strcpy(programStats.move_name, mvname);
7384                 programStats.ok_to_send = 1;
7385                 programStats.movelist[0] = '\0';
7386
7387                 SendProgramStatsToFrontend( cps, &programStats );
7388
7389                 return;
7390
7391             } else if (strncmp(message,"++",2) == 0) {
7392                 /* Crafty 9.29+ outputs this */
7393                 programStats.got_fail = 2;
7394                 return;
7395
7396             } else if (strncmp(message,"--",2) == 0) {
7397                 /* Crafty 9.29+ outputs this */
7398                 programStats.got_fail = 1;
7399                 return;
7400
7401             } else if (thinkOutput[0] != NULLCHAR &&
7402                        strncmp(message, "    ", 4) == 0) {
7403                 unsigned message_len;
7404
7405                 p = message;
7406                 while (*p && *p == ' ') p++;
7407
7408                 message_len = strlen( p );
7409
7410                 /* [AS] Avoid buffer overflow */
7411                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7412                     strcat(thinkOutput, " ");
7413                     strcat(thinkOutput, p);
7414                 }
7415
7416                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7417                     strcat(programStats.movelist, " ");
7418                     strcat(programStats.movelist, p);
7419                 }
7420
7421                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7422                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7423                     DisplayMove(currentMove - 1);
7424                 }
7425                 return;
7426             }
7427         }
7428         else {
7429             buf1[0] = NULLCHAR;
7430
7431             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7432                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7433             {
7434                 ChessProgramStats cpstats;
7435
7436                 if (plyext != ' ' && plyext != '\t') {
7437                     time *= 100;
7438                 }
7439
7440                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7441                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7442                     curscore = -curscore;
7443                 }
7444
7445                 cpstats.depth = plylev;
7446                 cpstats.nodes = nodes;
7447                 cpstats.time = time;
7448                 cpstats.score = curscore;
7449                 cpstats.got_only_move = 0;
7450                 cpstats.movelist[0] = '\0';
7451
7452                 if (buf1[0] != NULLCHAR) {
7453                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7454                 }
7455
7456                 cpstats.ok_to_send = 0;
7457                 cpstats.line_is_book = 0;
7458                 cpstats.nr_moves = 0;
7459                 cpstats.moves_left = 0;
7460
7461                 SendProgramStatsToFrontend( cps, &cpstats );
7462             }
7463         }
7464     }
7465 }
7466
7467
7468 /* Parse a game score from the character string "game", and
7469    record it as the history of the current game.  The game
7470    score is NOT assumed to start from the standard position.
7471    The display is not updated in any way.
7472    */
7473 void
7474 ParseGameHistory(game)
7475      char *game;
7476 {
7477     ChessMove moveType;
7478     int fromX, fromY, toX, toY, boardIndex;
7479     char promoChar;
7480     char *p, *q;
7481     char buf[MSG_SIZ];
7482
7483     if (appData.debugMode)
7484       fprintf(debugFP, "Parsing game history: %s\n", game);
7485
7486     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7487     gameInfo.site = StrSave(appData.icsHost);
7488     gameInfo.date = PGNDate();
7489     gameInfo.round = StrSave("-");
7490
7491     /* Parse out names of players */
7492     while (*game == ' ') game++;
7493     p = buf;
7494     while (*game != ' ') *p++ = *game++;
7495     *p = NULLCHAR;
7496     gameInfo.white = StrSave(buf);
7497     while (*game == ' ') game++;
7498     p = buf;
7499     while (*game != ' ' && *game != '\n') *p++ = *game++;
7500     *p = NULLCHAR;
7501     gameInfo.black = StrSave(buf);
7502
7503     /* Parse moves */
7504     boardIndex = blackPlaysFirst ? 1 : 0;
7505     yynewstr(game);
7506     for (;;) {
7507         yyboardindex = boardIndex;
7508         moveType = (ChessMove) yylex();
7509         switch (moveType) {
7510           case IllegalMove:             /* maybe suicide chess, etc. */
7511   if (appData.debugMode) {
7512     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7513     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7514     setbuf(debugFP, NULL);
7515   }
7516           case WhitePromotionChancellor:
7517           case BlackPromotionChancellor:
7518           case WhitePromotionArchbishop:
7519           case BlackPromotionArchbishop:
7520           case WhitePromotionQueen:
7521           case BlackPromotionQueen:
7522           case WhitePromotionRook:
7523           case BlackPromotionRook:
7524           case WhitePromotionBishop:
7525           case BlackPromotionBishop:
7526           case WhitePromotionKnight:
7527           case BlackPromotionKnight:
7528           case WhitePromotionKing:
7529           case BlackPromotionKing:
7530           case NormalMove:
7531           case WhiteCapturesEnPassant:
7532           case BlackCapturesEnPassant:
7533           case WhiteKingSideCastle:
7534           case WhiteQueenSideCastle:
7535           case BlackKingSideCastle:
7536           case BlackQueenSideCastle:
7537           case WhiteKingSideCastleWild:
7538           case WhiteQueenSideCastleWild:
7539           case BlackKingSideCastleWild:
7540           case BlackQueenSideCastleWild:
7541           /* PUSH Fabien */
7542           case WhiteHSideCastleFR:
7543           case WhiteASideCastleFR:
7544           case BlackHSideCastleFR:
7545           case BlackASideCastleFR:
7546           /* POP Fabien */
7547             fromX = currentMoveString[0] - AAA;
7548             fromY = currentMoveString[1] - ONE;
7549             toX = currentMoveString[2] - AAA;
7550             toY = currentMoveString[3] - ONE;
7551             promoChar = currentMoveString[4];
7552             break;
7553           case WhiteDrop:
7554           case BlackDrop:
7555             fromX = moveType == WhiteDrop ?
7556               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7557             (int) CharToPiece(ToLower(currentMoveString[0]));
7558             fromY = DROP_RANK;
7559             toX = currentMoveString[2] - AAA;
7560             toY = currentMoveString[3] - ONE;
7561             promoChar = NULLCHAR;
7562             break;
7563           case AmbiguousMove:
7564             /* bug? */
7565             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7566   if (appData.debugMode) {
7567     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7568     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7569     setbuf(debugFP, NULL);
7570   }
7571             DisplayError(buf, 0);
7572             return;
7573           case ImpossibleMove:
7574             /* bug? */
7575             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7576   if (appData.debugMode) {
7577     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7578     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7579     setbuf(debugFP, NULL);
7580   }
7581             DisplayError(buf, 0);
7582             return;
7583           case (ChessMove) 0:   /* end of file */
7584             if (boardIndex < backwardMostMove) {
7585                 /* Oops, gap.  How did that happen? */
7586                 DisplayError(_("Gap in move list"), 0);
7587                 return;
7588             }
7589             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7590             if (boardIndex > forwardMostMove) {
7591                 forwardMostMove = boardIndex;
7592             }
7593             return;
7594           case ElapsedTime:
7595             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7596                 strcat(parseList[boardIndex-1], " ");
7597                 strcat(parseList[boardIndex-1], yy_text);
7598             }
7599             continue;
7600           case Comment:
7601           case PGNTag:
7602           case NAG:
7603           default:
7604             /* ignore */
7605             continue;
7606           case WhiteWins:
7607           case BlackWins:
7608           case GameIsDrawn:
7609           case GameUnfinished:
7610             if (gameMode == IcsExamining) {
7611                 if (boardIndex < backwardMostMove) {
7612                     /* Oops, gap.  How did that happen? */
7613                     return;
7614                 }
7615                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7616                 return;
7617             }
7618             gameInfo.result = moveType;
7619             p = strchr(yy_text, '{');
7620             if (p == NULL) p = strchr(yy_text, '(');
7621             if (p == NULL) {
7622                 p = yy_text;
7623                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7624             } else {
7625                 q = strchr(p, *p == '{' ? '}' : ')');
7626                 if (q != NULL) *q = NULLCHAR;
7627                 p++;
7628             }
7629             gameInfo.resultDetails = StrSave(p);
7630             continue;
7631         }
7632         if (boardIndex >= forwardMostMove &&
7633             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7634             backwardMostMove = blackPlaysFirst ? 1 : 0;
7635             return;
7636         }
7637         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7638                                  fromY, fromX, toY, toX, promoChar,
7639                                  parseList[boardIndex]);
7640         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7641         /* currentMoveString is set as a side-effect of yylex */
7642         strcpy(moveList[boardIndex], currentMoveString);
7643         strcat(moveList[boardIndex], "\n");
7644         boardIndex++;
7645         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7646         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7647           case MT_NONE:
7648           case MT_STALEMATE:
7649           default:
7650             break;
7651           case MT_CHECK:
7652             if(gameInfo.variant != VariantShogi)
7653                 strcat(parseList[boardIndex - 1], "+");
7654             break;
7655           case MT_CHECKMATE:
7656           case MT_STAINMATE:
7657             strcat(parseList[boardIndex - 1], "#");
7658             break;
7659         }
7660     }
7661 }
7662
7663
7664 /* Apply a move to the given board  */
7665 void
7666 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7667      int fromX, fromY, toX, toY;
7668      int promoChar;
7669      Board board;
7670 {
7671   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7672   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7673
7674     /* [HGM] compute & store e.p. status and castling rights for new position */
7675     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7676     { int i;
7677
7678       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7679       oldEP = (signed char)board[EP_STATUS];
7680       board[EP_STATUS] = EP_NONE;
7681
7682       if( board[toY][toX] != EmptySquare ) 
7683            board[EP_STATUS] = EP_CAPTURE;  
7684
7685       if( board[fromY][fromX] == WhitePawn ) {
7686            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7687                board[EP_STATUS] = EP_PAWN_MOVE;
7688            if( toY-fromY==2) {
7689                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7690                         gameInfo.variant != VariantBerolina || toX < fromX)
7691                       board[EP_STATUS] = toX | berolina;
7692                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7693                   gameInfo.variant != VariantBerolina || toX > fromX) 
7694                  board[EP_STATUS] = toX;
7695            }
7696       } else
7697       if( board[fromY][fromX] == BlackPawn ) {
7698            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7699                board[EP_STATUS] = EP_PAWN_MOVE; 
7700            if( toY-fromY== -2) {
7701                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7702                         gameInfo.variant != VariantBerolina || toX < fromX)
7703                       board[EP_STATUS] = toX | berolina;
7704                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7705                         gameInfo.variant != VariantBerolina || toX > fromX) 
7706                       board[EP_STATUS] = toX;
7707            }
7708        }
7709
7710        for(i=0; i<nrCastlingRights; i++) {
7711            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7712               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7713              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7714        }
7715
7716     }
7717
7718   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7719   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7720        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7721
7722   if (fromX == toX && fromY == toY) return;
7723
7724   if (fromY == DROP_RANK) {
7725         /* must be first */
7726         piece = board[toY][toX] = (ChessSquare) fromX;
7727   } else {
7728      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7729      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7730      if(gameInfo.variant == VariantKnightmate)
7731          king += (int) WhiteUnicorn - (int) WhiteKing;
7732
7733     /* Code added by Tord: */
7734     /* FRC castling assumed when king captures friendly rook. */
7735     if (board[fromY][fromX] == WhiteKing &&
7736              board[toY][toX] == WhiteRook) {
7737       board[fromY][fromX] = EmptySquare;
7738       board[toY][toX] = EmptySquare;
7739       if(toX > fromX) {
7740         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7741       } else {
7742         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7743       }
7744     } else if (board[fromY][fromX] == BlackKing &&
7745                board[toY][toX] == BlackRook) {
7746       board[fromY][fromX] = EmptySquare;
7747       board[toY][toX] = EmptySquare;
7748       if(toX > fromX) {
7749         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7750       } else {
7751         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7752       }
7753     /* End of code added by Tord */
7754
7755     } else if (board[fromY][fromX] == king
7756         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7757         && toY == fromY && toX > fromX+1) {
7758         board[fromY][fromX] = EmptySquare;
7759         board[toY][toX] = king;
7760         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7761         board[fromY][BOARD_RGHT-1] = EmptySquare;
7762     } else if (board[fromY][fromX] == king
7763         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7764                && toY == fromY && toX < fromX-1) {
7765         board[fromY][fromX] = EmptySquare;
7766         board[toY][toX] = king;
7767         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7768         board[fromY][BOARD_LEFT] = EmptySquare;
7769     } else if (board[fromY][fromX] == WhitePawn
7770                && toY >= BOARD_HEIGHT-promoRank
7771                && gameInfo.variant != VariantXiangqi
7772                ) {
7773         /* white pawn promotion */
7774         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7775         if (board[toY][toX] == EmptySquare) {
7776             board[toY][toX] = WhiteQueen;
7777         }
7778         if(gameInfo.variant==VariantBughouse ||
7779            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7780             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7781         board[fromY][fromX] = EmptySquare;
7782     } else if ((fromY == BOARD_HEIGHT-4)
7783                && (toX != fromX)
7784                && gameInfo.variant != VariantXiangqi
7785                && gameInfo.variant != VariantBerolina
7786                && (board[fromY][fromX] == WhitePawn)
7787                && (board[toY][toX] == EmptySquare)) {
7788         board[fromY][fromX] = EmptySquare;
7789         board[toY][toX] = WhitePawn;
7790         captured = board[toY - 1][toX];
7791         board[toY - 1][toX] = EmptySquare;
7792     } else if ((fromY == BOARD_HEIGHT-4)
7793                && (toX == fromX)
7794                && gameInfo.variant == VariantBerolina
7795                && (board[fromY][fromX] == WhitePawn)
7796                && (board[toY][toX] == EmptySquare)) {
7797         board[fromY][fromX] = EmptySquare;
7798         board[toY][toX] = WhitePawn;
7799         if(oldEP & EP_BEROLIN_A) {
7800                 captured = board[fromY][fromX-1];
7801                 board[fromY][fromX-1] = EmptySquare;
7802         }else{  captured = board[fromY][fromX+1];
7803                 board[fromY][fromX+1] = EmptySquare;
7804         }
7805     } else if (board[fromY][fromX] == king
7806         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7807                && toY == fromY && toX > fromX+1) {
7808         board[fromY][fromX] = EmptySquare;
7809         board[toY][toX] = king;
7810         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7811         board[fromY][BOARD_RGHT-1] = EmptySquare;
7812     } else if (board[fromY][fromX] == king
7813         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7814                && toY == fromY && toX < fromX-1) {
7815         board[fromY][fromX] = EmptySquare;
7816         board[toY][toX] = king;
7817         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7818         board[fromY][BOARD_LEFT] = EmptySquare;
7819     } else if (fromY == 7 && fromX == 3
7820                && board[fromY][fromX] == BlackKing
7821                && toY == 7 && toX == 5) {
7822         board[fromY][fromX] = EmptySquare;
7823         board[toY][toX] = BlackKing;
7824         board[fromY][7] = EmptySquare;
7825         board[toY][4] = BlackRook;
7826     } else if (fromY == 7 && fromX == 3
7827                && board[fromY][fromX] == BlackKing
7828                && toY == 7 && toX == 1) {
7829         board[fromY][fromX] = EmptySquare;
7830         board[toY][toX] = BlackKing;
7831         board[fromY][0] = EmptySquare;
7832         board[toY][2] = BlackRook;
7833     } else if (board[fromY][fromX] == BlackPawn
7834                && toY < promoRank
7835                && gameInfo.variant != VariantXiangqi
7836                ) {
7837         /* black pawn promotion */
7838         board[toY][toX] = CharToPiece(ToLower(promoChar));
7839         if (board[toY][toX] == EmptySquare) {
7840             board[toY][toX] = BlackQueen;
7841         }
7842         if(gameInfo.variant==VariantBughouse ||
7843            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7844             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7845         board[fromY][fromX] = EmptySquare;
7846     } else if ((fromY == 3)
7847                && (toX != fromX)
7848                && gameInfo.variant != VariantXiangqi
7849                && gameInfo.variant != VariantBerolina
7850                && (board[fromY][fromX] == BlackPawn)
7851                && (board[toY][toX] == EmptySquare)) {
7852         board[fromY][fromX] = EmptySquare;
7853         board[toY][toX] = BlackPawn;
7854         captured = board[toY + 1][toX];
7855         board[toY + 1][toX] = EmptySquare;
7856     } else if ((fromY == 3)
7857                && (toX == fromX)
7858                && gameInfo.variant == VariantBerolina
7859                && (board[fromY][fromX] == BlackPawn)
7860                && (board[toY][toX] == EmptySquare)) {
7861         board[fromY][fromX] = EmptySquare;
7862         board[toY][toX] = BlackPawn;
7863         if(oldEP & EP_BEROLIN_A) {
7864                 captured = board[fromY][fromX-1];
7865                 board[fromY][fromX-1] = EmptySquare;
7866         }else{  captured = board[fromY][fromX+1];
7867                 board[fromY][fromX+1] = EmptySquare;
7868         }
7869     } else {
7870         board[toY][toX] = board[fromY][fromX];
7871         board[fromY][fromX] = EmptySquare;
7872     }
7873
7874     /* [HGM] now we promote for Shogi, if needed */
7875     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7876         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7877   }
7878
7879     if (gameInfo.holdingsWidth != 0) {
7880
7881       /* !!A lot more code needs to be written to support holdings  */
7882       /* [HGM] OK, so I have written it. Holdings are stored in the */
7883       /* penultimate board files, so they are automaticlly stored   */
7884       /* in the game history.                                       */
7885       if (fromY == DROP_RANK) {
7886         /* Delete from holdings, by decreasing count */
7887         /* and erasing image if necessary            */
7888         p = (int) fromX;
7889         if(p < (int) BlackPawn) { /* white drop */
7890              p -= (int)WhitePawn;
7891                  p = PieceToNumber((ChessSquare)p);
7892              if(p >= gameInfo.holdingsSize) p = 0;
7893              if(--board[p][BOARD_WIDTH-2] <= 0)
7894                   board[p][BOARD_WIDTH-1] = EmptySquare;
7895              if((int)board[p][BOARD_WIDTH-2] < 0)
7896                         board[p][BOARD_WIDTH-2] = 0;
7897         } else {                  /* black drop */
7898              p -= (int)BlackPawn;
7899                  p = PieceToNumber((ChessSquare)p);
7900              if(p >= gameInfo.holdingsSize) p = 0;
7901              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7902                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7903              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7904                         board[BOARD_HEIGHT-1-p][1] = 0;
7905         }
7906       }
7907       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7908           && gameInfo.variant != VariantBughouse        ) {
7909         /* [HGM] holdings: Add to holdings, if holdings exist */
7910         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7911                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7912                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7913         }
7914         p = (int) captured;
7915         if (p >= (int) BlackPawn) {
7916           p -= (int)BlackPawn;
7917           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7918                   /* in Shogi restore piece to its original  first */
7919                   captured = (ChessSquare) (DEMOTED captured);
7920                   p = DEMOTED p;
7921           }
7922           p = PieceToNumber((ChessSquare)p);
7923           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7924           board[p][BOARD_WIDTH-2]++;
7925           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7926         } else {
7927           p -= (int)WhitePawn;
7928           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7929                   captured = (ChessSquare) (DEMOTED captured);
7930                   p = DEMOTED p;
7931           }
7932           p = PieceToNumber((ChessSquare)p);
7933           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7934           board[BOARD_HEIGHT-1-p][1]++;
7935           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7936         }
7937       }
7938     } else if (gameInfo.variant == VariantAtomic) {
7939       if (captured != EmptySquare) {
7940         int y, x;
7941         for (y = toY-1; y <= toY+1; y++) {
7942           for (x = toX-1; x <= toX+1; x++) {
7943             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7944                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7945               board[y][x] = EmptySquare;
7946             }
7947           }
7948         }
7949         board[toY][toX] = EmptySquare;
7950       }
7951     }
7952     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7953         /* [HGM] Shogi promotions */
7954         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7955     }
7956
7957     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7958                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7959         // [HGM] superchess: take promotion piece out of holdings
7960         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7961         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7962             if(!--board[k][BOARD_WIDTH-2])
7963                 board[k][BOARD_WIDTH-1] = EmptySquare;
7964         } else {
7965             if(!--board[BOARD_HEIGHT-1-k][1])
7966                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7967         }
7968     }
7969
7970 }
7971
7972 /* Updates forwardMostMove */
7973 void
7974 MakeMove(fromX, fromY, toX, toY, promoChar)
7975      int fromX, fromY, toX, toY;
7976      int promoChar;
7977 {
7978 //    forwardMostMove++; // [HGM] bare: moved downstream
7979
7980     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7981         int timeLeft; static int lastLoadFlag=0; int king, piece;
7982         piece = boards[forwardMostMove][fromY][fromX];
7983         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7984         if(gameInfo.variant == VariantKnightmate)
7985             king += (int) WhiteUnicorn - (int) WhiteKing;
7986         if(forwardMostMove == 0) {
7987             if(blackPlaysFirst)
7988                 fprintf(serverMoves, "%s;", second.tidy);
7989             fprintf(serverMoves, "%s;", first.tidy);
7990             if(!blackPlaysFirst)
7991                 fprintf(serverMoves, "%s;", second.tidy);
7992         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7993         lastLoadFlag = loadFlag;
7994         // print base move
7995         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7996         // print castling suffix
7997         if( toY == fromY && piece == king ) {
7998             if(toX-fromX > 1)
7999                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8000             if(fromX-toX >1)
8001                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8002         }
8003         // e.p. suffix
8004         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8005              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8006              boards[forwardMostMove][toY][toX] == EmptySquare
8007              && fromX != toX )
8008                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8009         // promotion suffix
8010         if(promoChar != NULLCHAR)
8011                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8012         if(!loadFlag) {
8013             fprintf(serverMoves, "/%d/%d",
8014                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8015             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8016             else                      timeLeft = blackTimeRemaining/1000;
8017             fprintf(serverMoves, "/%d", timeLeft);
8018         }
8019         fflush(serverMoves);
8020     }
8021
8022     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8023       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8024                         0, 1);
8025       return;
8026     }
8027     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8028     if (commentList[forwardMostMove+1] != NULL) {
8029         free(commentList[forwardMostMove+1]);
8030         commentList[forwardMostMove+1] = NULL;
8031     }
8032     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8033     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8034     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8035     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8036     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8037     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8038     gameInfo.result = GameUnfinished;
8039     if (gameInfo.resultDetails != NULL) {
8040         free(gameInfo.resultDetails);
8041         gameInfo.resultDetails = NULL;
8042     }
8043     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8044                               moveList[forwardMostMove - 1]);
8045     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8046                              PosFlags(forwardMostMove - 1),
8047                              fromY, fromX, toY, toX, promoChar,
8048                              parseList[forwardMostMove - 1]);
8049     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8050       case MT_NONE:
8051       case MT_STALEMATE:
8052       default:
8053         break;
8054       case MT_CHECK:
8055         if(gameInfo.variant != VariantShogi)
8056             strcat(parseList[forwardMostMove - 1], "+");
8057         break;
8058       case MT_CHECKMATE:
8059       case MT_STAINMATE:
8060         strcat(parseList[forwardMostMove - 1], "#");
8061         break;
8062     }
8063     if (appData.debugMode) {
8064         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8065     }
8066
8067 }
8068
8069 /* Updates currentMove if not pausing */
8070 void
8071 ShowMove(fromX, fromY, toX, toY)
8072 {
8073     int instant = (gameMode == PlayFromGameFile) ?
8074         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8075
8076     if(appData.noGUI) return;
8077
8078     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8079       {
8080         if (!instant)
8081           {
8082             if (forwardMostMove == currentMove + 1)
8083               {
8084 //TODO
8085 //              AnimateMove(boards[forwardMostMove - 1],
8086 //                          fromX, fromY, toX, toY);
8087               }
8088             if (appData.highlightLastMove)
8089               {
8090                 SetHighlights(fromX, fromY, toX, toY);
8091               }
8092           }
8093         currentMove = forwardMostMove;
8094     }
8095
8096     if (instant) return;
8097
8098     DisplayMove(currentMove - 1);
8099     DrawPosition(FALSE, boards[currentMove]);
8100     DisplayBothClocks();
8101     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8102
8103     return;
8104 }
8105
8106 void SendEgtPath(ChessProgramState *cps)
8107 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8108         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8109
8110         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8111
8112         while(*p) {
8113             char c, *q = name+1, *r, *s;
8114
8115             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8116             while(*p && *p != ',') *q++ = *p++;
8117             *q++ = ':'; *q = 0;
8118             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8119                 strcmp(name, ",nalimov:") == 0 ) {
8120                 // take nalimov path from the menu-changeable option first, if it is defined
8121                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8122                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8123             } else
8124             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8125                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8126                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8127                 s = r = StrStr(s, ":") + 1; // beginning of path info
8128                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8129                 c = *r; *r = 0;             // temporarily null-terminate path info
8130                     *--q = 0;               // strip of trailig ':' from name
8131                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8132                 *r = c;
8133                 SendToProgram(buf,cps);     // send egtbpath command for this format
8134             }
8135             if(*p == ',') p++; // read away comma to position for next format name
8136         }
8137 }
8138
8139 void
8140 InitChessProgram(cps, setup)
8141      ChessProgramState *cps;
8142      int setup; /* [HGM] needed to setup FRC opening position */
8143 {
8144     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8145     if (appData.noChessProgram) return;
8146     hintRequested = FALSE;
8147     bookRequested = FALSE;
8148
8149     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8150     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8151     if(cps->memSize) { /* [HGM] memory */
8152         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8153         SendToProgram(buf, cps);
8154     }
8155     SendEgtPath(cps); /* [HGM] EGT */
8156     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8157         sprintf(buf, "cores %d\n", appData.smpCores);
8158         SendToProgram(buf, cps);
8159     }
8160
8161     SendToProgram(cps->initString, cps);
8162     if (gameInfo.variant != VariantNormal &&
8163         gameInfo.variant != VariantLoadable
8164         /* [HGM] also send variant if board size non-standard */
8165         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8166                                             ) {
8167       char *v = VariantName(gameInfo.variant);
8168       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8169         /* [HGM] in protocol 1 we have to assume all variants valid */
8170         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8171         DisplayFatalError(buf, 0, 1);
8172         return;
8173       }
8174
8175       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8176       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8177       if( gameInfo.variant == VariantXiangqi )
8178            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8179       if( gameInfo.variant == VariantShogi )
8180            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8181       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8182            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8183       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8184                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8185            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8186       if( gameInfo.variant == VariantCourier )
8187            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8188       if( gameInfo.variant == VariantSuper )
8189            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8190       if( gameInfo.variant == VariantGreat )
8191            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8192
8193       if(overruled) {
8194            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8195                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8196            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8197            if(StrStr(cps->variants, b) == NULL) {
8198                // specific sized variant not known, check if general sizing allowed
8199                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8200                    if(StrStr(cps->variants, "boardsize") == NULL) {
8201                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8202                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8203                        DisplayFatalError(buf, 0, 1);
8204                        return;
8205                    }
8206                    /* [HGM] here we really should compare with the maximum supported board size */
8207                }
8208            }
8209       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8210       sprintf(buf, "variant %s\n", b);
8211       SendToProgram(buf, cps);
8212     }
8213     currentlyInitializedVariant = gameInfo.variant;
8214
8215     /* [HGM] send opening position in FRC to first engine */
8216     if(setup) {
8217           SendToProgram("force\n", cps);
8218           SendBoard(cps, 0);
8219           /* engine is now in force mode! Set flag to wake it up after first move. */
8220           setboardSpoiledMachineBlack = 1;
8221     }
8222
8223     if (cps->sendICS) {
8224       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8225       SendToProgram(buf, cps);
8226     }
8227     cps->maybeThinking = FALSE;
8228     cps->offeredDraw = 0;
8229     if (!appData.icsActive) {
8230         SendTimeControl(cps, movesPerSession, timeControl,
8231                         timeIncrement, appData.searchDepth,
8232                         searchTime);
8233     }
8234     if (appData.showThinking
8235         // [HGM] thinking: four options require thinking output to be sent
8236         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8237                                 ) {
8238         SendToProgram("post\n", cps);
8239     }
8240     SendToProgram("hard\n", cps);
8241     if (!appData.ponderNextMove) {
8242         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8243            it without being sure what state we are in first.  "hard"
8244            is not a toggle, so that one is OK.
8245          */
8246         SendToProgram("easy\n", cps);
8247     }
8248     if (cps->usePing) {
8249       sprintf(buf, "ping %d\n", ++cps->lastPing);
8250       SendToProgram(buf, cps);
8251     }
8252     cps->initDone = TRUE;
8253 }
8254
8255
8256 void
8257 StartChessProgram(cps)
8258      ChessProgramState *cps;
8259 {
8260     char buf[MSG_SIZ];
8261     int err;
8262
8263     if (appData.noChessProgram) return;
8264     cps->initDone = FALSE;
8265
8266     if (strcmp(cps->host, "localhost") == 0) {
8267         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8268     } else if (*appData.remoteShell == NULLCHAR) {
8269         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8270     } else {
8271         if (*appData.remoteUser == NULLCHAR) {
8272           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8273                     cps->program);
8274         } else {
8275           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8276                     cps->host, appData.remoteUser, cps->program);
8277         }
8278         err = StartChildProcess(buf, "", &cps->pr);
8279     }
8280
8281     if (err != 0) {
8282         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8283         DisplayFatalError(buf, err, 1);
8284         cps->pr = NoProc;
8285         cps->isr = NULL;
8286         return;
8287     }
8288
8289     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8290     if (cps->protocolVersion > 1) {
8291       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8292       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8293       cps->comboCnt = 0;  //                and values of combo boxes
8294       SendToProgram(buf, cps);
8295     } else {
8296       SendToProgram("xboard\n", cps);
8297     }
8298 }
8299
8300
8301 void
8302 TwoMachinesEventIfReady P((void))
8303 {
8304   if (first.lastPing != first.lastPong) {
8305     DisplayMessage("", _("Waiting for first chess program"));
8306     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8307     return;
8308   }
8309   if (second.lastPing != second.lastPong) {
8310     DisplayMessage("", _("Waiting for second chess program"));
8311     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8312     return;
8313   }
8314   ThawUI();
8315   TwoMachinesEvent();
8316 }
8317
8318 void
8319 NextMatchGame P((void))
8320 {
8321     int index; /* [HGM] autoinc: step load index during match */
8322     Reset(FALSE, TRUE);
8323     if (*appData.loadGameFile != NULLCHAR) {
8324         index = appData.loadGameIndex;
8325         if(index < 0) { // [HGM] autoinc
8326             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8327             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8328         }
8329         LoadGameFromFile(appData.loadGameFile,
8330                          index,
8331                          appData.loadGameFile, FALSE);
8332     } else if (*appData.loadPositionFile != NULLCHAR) {
8333         index = appData.loadPositionIndex;
8334         if(index < 0) { // [HGM] autoinc
8335             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8336             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8337         }
8338         LoadPositionFromFile(appData.loadPositionFile,
8339                              index,
8340                              appData.loadPositionFile);
8341     }
8342     TwoMachinesEventIfReady();
8343 }
8344
8345 void UserAdjudicationEvent( int result )
8346 {
8347     ChessMove gameResult = GameIsDrawn;
8348
8349     if( result > 0 ) {
8350         gameResult = WhiteWins;
8351     }
8352     else if( result < 0 ) {
8353         gameResult = BlackWins;
8354     }
8355
8356     if( gameMode == TwoMachinesPlay ) {
8357         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8358     }
8359 }
8360
8361
8362 // [HGM] save: calculate checksum of game to make games easily identifiable
8363 int StringCheckSum(char *s)
8364 {
8365         int i = 0;
8366         if(s==NULL) return 0;
8367         while(*s) i = i*259 + *s++;
8368         return i;
8369 }
8370
8371 int GameCheckSum()
8372 {
8373         int i, sum=0;
8374         for(i=backwardMostMove; i<forwardMostMove; i++) {
8375                 sum += pvInfoList[i].depth;
8376                 sum += StringCheckSum(parseList[i]);
8377                 sum += StringCheckSum(commentList[i]);
8378                 sum *= 261;
8379         }
8380         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8381         return sum + StringCheckSum(commentList[i]);
8382 } // end of save patch
8383
8384 void
8385 GameEnds(result, resultDetails, whosays)
8386      ChessMove result;
8387      char *resultDetails;
8388      int whosays;
8389 {
8390     GameMode nextGameMode;
8391     int isIcsGame;
8392     char buf[MSG_SIZ];
8393
8394     if(endingGame) return; /* [HGM] crash: forbid recursion */
8395     endingGame = 1;
8396
8397     if (appData.debugMode) {
8398       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8399               result, resultDetails ? resultDetails : "(null)", whosays);
8400     }
8401
8402     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8403         /* If we are playing on ICS, the server decides when the
8404            game is over, but the engine can offer to draw, claim
8405            a draw, or resign.
8406          */
8407 #if ZIPPY
8408         if (appData.zippyPlay && first.initDone) {
8409             if (result == GameIsDrawn) {
8410                 /* In case draw still needs to be claimed */
8411                 SendToICS(ics_prefix);
8412                 SendToICS("draw\n");
8413             } else if (StrCaseStr(resultDetails, "resign")) {
8414                 SendToICS(ics_prefix);
8415                 SendToICS("resign\n");
8416             }
8417         }
8418 #endif
8419         endingGame = 0; /* [HGM] crash */
8420         return;
8421     }
8422
8423     /* If we're loading the game from a file, stop */
8424     if (whosays == GE_FILE) {
8425       (void) StopLoadGameTimer();
8426       gameFileFP = NULL;
8427     }
8428
8429     /* Cancel draw offers */
8430     first.offeredDraw = second.offeredDraw = 0;
8431
8432     /* If this is an ICS game, only ICS can really say it's done;
8433        if not, anyone can. */
8434     isIcsGame = (gameMode == IcsPlayingWhite ||
8435                  gameMode == IcsPlayingBlack ||
8436                  gameMode == IcsObserving    ||
8437                  gameMode == IcsExamining);
8438
8439     if (!isIcsGame || whosays == GE_ICS) {
8440         /* OK -- not an ICS game, or ICS said it was done */
8441         StopClocks();
8442         if (!isIcsGame && !appData.noChessProgram)
8443           SetUserThinkingEnables();
8444
8445         /* [HGM] if a machine claims the game end we verify this claim */
8446         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8447             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8448                 char claimer;
8449                 ChessMove trueResult = (ChessMove) -1;
8450
8451                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8452                                             first.twoMachinesColor[0] :
8453                                             second.twoMachinesColor[0] ;
8454
8455                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8456                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8457                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8458                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8459                 } else
8460                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8461                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8462                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8463                 } else
8464                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8465                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8466                 }
8467
8468                 // now verify win claims, but not in drop games, as we don't understand those yet
8469                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8470                                                  || gameInfo.variant == VariantGreat) &&
8471                     (result == WhiteWins && claimer == 'w' ||
8472                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8473                       if (appData.debugMode) {
8474                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8475                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8476                       }
8477                       if(result != trueResult) {
8478                               sprintf(buf, "False win claim: '%s'", resultDetails);
8479                               result = claimer == 'w' ? BlackWins : WhiteWins;
8480                               resultDetails = buf;
8481                       }
8482                 } else
8483                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8484                     && (forwardMostMove <= backwardMostMove ||
8485                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8486                         (claimer=='b')==(forwardMostMove&1))
8487                                                                                   ) {
8488                       /* [HGM] verify: draws that were not flagged are false claims */
8489                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8490                       result = claimer == 'w' ? BlackWins : WhiteWins;
8491                       resultDetails = buf;
8492                 }
8493                 /* (Claiming a loss is accepted no questions asked!) */
8494             }
8495
8496             /* [HGM] bare: don't allow bare King to win */
8497             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8498                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8499                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8500                && result != GameIsDrawn)
8501             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8502                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8503                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8504                         if(p >= 0 && p <= (int)WhiteKing) k++;
8505                 }
8506                 if (appData.debugMode) {
8507                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8508                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8509                 }
8510                 if(k <= 1) {
8511                         result = GameIsDrawn;
8512                         sprintf(buf, "%s but bare king", resultDetails);
8513                         resultDetails = buf;
8514                 }
8515             }
8516         }
8517
8518         if(serverMoves != NULL && !loadFlag) { char c = '=';
8519             if(result==WhiteWins) c = '+';
8520             if(result==BlackWins) c = '-';
8521             if(resultDetails != NULL)
8522                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8523         }
8524         if (resultDetails != NULL) {
8525             gameInfo.result = result;
8526             gameInfo.resultDetails = StrSave(resultDetails);
8527
8528             /* display last move only if game was not loaded from file */
8529             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8530                 DisplayMove(currentMove - 1);
8531
8532             if (forwardMostMove != 0) {
8533                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8534                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8535                                                                 ) {
8536                     if (*appData.saveGameFile != NULLCHAR) {
8537                         SaveGameToFile(appData.saveGameFile, TRUE);
8538                     } else if (appData.autoSaveGames) {
8539                         AutoSaveGame();
8540                     }
8541                     if (*appData.savePositionFile != NULLCHAR) {
8542                         SavePositionToFile(appData.savePositionFile);
8543                     }
8544                 }
8545             }
8546
8547             /* Tell program how game ended in case it is learning */
8548             /* [HGM] Moved this to after saving the PGN, just in case */
8549             /* engine died and we got here through time loss. In that */
8550             /* case we will get a fatal error writing the pipe, which */
8551             /* would otherwise lose us the PGN.                       */
8552             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8553             /* output during GameEnds should never be fatal anymore   */
8554             if (gameMode == MachinePlaysWhite ||
8555                 gameMode == MachinePlaysBlack ||
8556                 gameMode == TwoMachinesPlay ||
8557                 gameMode == IcsPlayingWhite ||
8558                 gameMode == IcsPlayingBlack ||
8559                 gameMode == BeginningOfGame) {
8560                 char buf[MSG_SIZ];
8561                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8562                         resultDetails);
8563                 if (first.pr != NoProc) {
8564                     SendToProgram(buf, &first);
8565                 }
8566                 if (second.pr != NoProc &&
8567                     gameMode == TwoMachinesPlay) {
8568                     SendToProgram(buf, &second);
8569                 }
8570             }
8571         }
8572
8573         if (appData.icsActive) {
8574             if (appData.quietPlay &&
8575                 (gameMode == IcsPlayingWhite ||
8576                  gameMode == IcsPlayingBlack)) {
8577                 SendToICS(ics_prefix);
8578                 SendToICS("set shout 1\n");
8579             }
8580             nextGameMode = IcsIdle;
8581             ics_user_moved = FALSE;
8582             /* clean up premove.  It's ugly when the game has ended and the
8583              * premove highlights are still on the board.
8584              */
8585             if (gotPremove) {
8586               gotPremove = FALSE;
8587               ClearPremoveHighlights();
8588               DrawPosition(FALSE, boards[currentMove]);
8589             }
8590             if (whosays == GE_ICS) {
8591                 switch (result) {
8592                 case WhiteWins:
8593                     if (gameMode == IcsPlayingWhite)
8594                         PlayIcsWinSound();
8595                     else if(gameMode == IcsPlayingBlack)
8596                         PlayIcsLossSound();
8597                     break;
8598                 case BlackWins:
8599                     if (gameMode == IcsPlayingBlack)
8600                         PlayIcsWinSound();
8601                     else if(gameMode == IcsPlayingWhite)
8602                         PlayIcsLossSound();
8603                     break;
8604                 case GameIsDrawn:
8605                     PlayIcsDrawSound();
8606                     break;
8607                 default:
8608                     PlayIcsUnfinishedSound();
8609                 }
8610             }
8611         } else if (gameMode == EditGame ||
8612                    gameMode == PlayFromGameFile ||
8613                    gameMode == AnalyzeMode ||
8614                    gameMode == AnalyzeFile) {
8615             nextGameMode = gameMode;
8616         } else {
8617             nextGameMode = EndOfGame;
8618         }
8619         pausing = FALSE;
8620         ModeHighlight();
8621     } else {
8622         nextGameMode = gameMode;
8623     }
8624
8625     if (appData.noChessProgram) {
8626         gameMode = nextGameMode;
8627         ModeHighlight();
8628         endingGame = 0; /* [HGM] crash */
8629         return;
8630     }
8631
8632     if (first.reuse) {
8633         /* Put first chess program into idle state */
8634         if (first.pr != NoProc &&
8635             (gameMode == MachinePlaysWhite ||
8636              gameMode == MachinePlaysBlack ||
8637              gameMode == TwoMachinesPlay ||
8638              gameMode == IcsPlayingWhite ||
8639              gameMode == IcsPlayingBlack ||
8640              gameMode == BeginningOfGame)) {
8641             SendToProgram("force\n", &first);
8642             if (first.usePing) {
8643               char buf[MSG_SIZ];
8644               sprintf(buf, "ping %d\n", ++first.lastPing);
8645               SendToProgram(buf, &first);
8646             }
8647         }
8648     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8649         /* Kill off first chess program */
8650         if (first.isr != NULL)
8651           RemoveInputSource(first.isr);
8652         first.isr = NULL;
8653
8654         if (first.pr != NoProc) {
8655             ExitAnalyzeMode();
8656             DoSleep( appData.delayBeforeQuit );
8657             SendToProgram("quit\n", &first);
8658             DoSleep( appData.delayAfterQuit );
8659             DestroyChildProcess(first.pr, first.useSigterm);
8660         }
8661         first.pr = NoProc;
8662     }
8663     if (second.reuse) {
8664         /* Put second chess program into idle state */
8665         if (second.pr != NoProc &&
8666             gameMode == TwoMachinesPlay) {
8667             SendToProgram("force\n", &second);
8668             if (second.usePing) {
8669               char buf[MSG_SIZ];
8670               sprintf(buf, "ping %d\n", ++second.lastPing);
8671               SendToProgram(buf, &second);
8672             }
8673         }
8674     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8675         /* Kill off second chess program */
8676         if (second.isr != NULL)
8677           RemoveInputSource(second.isr);
8678         second.isr = NULL;
8679
8680         if (second.pr != NoProc) {
8681             DoSleep( appData.delayBeforeQuit );
8682             SendToProgram("quit\n", &second);
8683             DoSleep( appData.delayAfterQuit );
8684             DestroyChildProcess(second.pr, second.useSigterm);
8685         }
8686         second.pr = NoProc;
8687     }
8688
8689     if (matchMode && gameMode == TwoMachinesPlay) {
8690         switch (result) {
8691         case WhiteWins:
8692           if (first.twoMachinesColor[0] == 'w') {
8693             first.matchWins++;
8694           } else {
8695             second.matchWins++;
8696           }
8697           break;
8698         case BlackWins:
8699           if (first.twoMachinesColor[0] == 'b') {
8700             first.matchWins++;
8701           } else {
8702             second.matchWins++;
8703           }
8704           break;
8705         default:
8706           break;
8707         }
8708         if (matchGame < appData.matchGames) {
8709             char *tmp;
8710             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8711                 tmp = first.twoMachinesColor;
8712                 first.twoMachinesColor = second.twoMachinesColor;
8713                 second.twoMachinesColor = tmp;
8714             }
8715             gameMode = nextGameMode;
8716             matchGame++;
8717             if(appData.matchPause>10000 || appData.matchPause<10)
8718                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8719             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8720             endingGame = 0; /* [HGM] crash */
8721             return;
8722         } else {
8723             char buf[MSG_SIZ];
8724             gameMode = nextGameMode;
8725             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8726                     first.tidy, second.tidy,
8727                     first.matchWins, second.matchWins,
8728                     appData.matchGames - (first.matchWins + second.matchWins));
8729             DisplayFatalError(buf, 0, 0);
8730         }
8731     }
8732     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8733         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8734       ExitAnalyzeMode();
8735     gameMode = nextGameMode;
8736     ModeHighlight();
8737     endingGame = 0;  /* [HGM] crash */
8738 }
8739
8740 /* Assumes program was just initialized (initString sent).
8741    Leaves program in force mode. */
8742 void
8743 FeedMovesToProgram(cps, upto)
8744      ChessProgramState *cps;
8745      int upto;
8746 {
8747     int i;
8748
8749     if (appData.debugMode)
8750       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8751               startedFromSetupPosition ? "position and " : "",
8752               backwardMostMove, upto, cps->which);
8753     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8754         // [HGM] variantswitch: make engine aware of new variant
8755         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8756                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8757         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8758         SendToProgram(buf, cps);
8759         currentlyInitializedVariant = gameInfo.variant;
8760     }
8761     SendToProgram("force\n", cps);
8762     if (startedFromSetupPosition) {
8763         SendBoard(cps, backwardMostMove);
8764     if (appData.debugMode) {
8765         fprintf(debugFP, "feedMoves\n");
8766     }
8767     }
8768     for (i = backwardMostMove; i < upto; i++) {
8769         SendMoveToProgram(i, cps);
8770     }
8771 }
8772
8773
8774 void
8775 ResurrectChessProgram()
8776 {
8777      /* The chess program may have exited.
8778         If so, restart it and feed it all the moves made so far. */
8779
8780     if (appData.noChessProgram || first.pr != NoProc) return;
8781
8782     StartChessProgram(&first);
8783     InitChessProgram(&first, FALSE);
8784     FeedMovesToProgram(&first, currentMove);
8785
8786     if (!first.sendTime) {
8787         /* can't tell gnuchess what its clock should read,
8788            so we bow to its notion. */
8789         ResetClocks();
8790         timeRemaining[0][currentMove] = whiteTimeRemaining;
8791         timeRemaining[1][currentMove] = blackTimeRemaining;
8792     }
8793
8794     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8795                 appData.icsEngineAnalyze) && first.analysisSupport) {
8796       SendToProgram("analyze\n", &first);
8797       first.analyzing = TRUE;
8798     }
8799 }
8800
8801 /*
8802  * Button procedures
8803  */
8804 void
8805 Reset(redraw, init)
8806      int redraw, init;
8807 {
8808     int i;
8809
8810     if (appData.debugMode) {
8811         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8812                 redraw, init, gameMode);
8813     }
8814     CleanupTail(); // [HGM] vari: delete any stored variations
8815     pausing = pauseExamInvalid = FALSE;
8816     startedFromSetupPosition = blackPlaysFirst = FALSE;
8817     firstMove = TRUE;
8818     whiteFlag = blackFlag = FALSE;
8819     userOfferedDraw = FALSE;
8820     hintRequested = bookRequested = FALSE;
8821     first.maybeThinking = FALSE;
8822     second.maybeThinking = FALSE;
8823     first.bookSuspend = FALSE; // [HGM] book
8824     second.bookSuspend = FALSE;
8825     thinkOutput[0] = NULLCHAR;
8826     lastHint[0] = NULLCHAR;
8827     ClearGameInfo(&gameInfo);
8828     gameInfo.variant = StringToVariant(appData.variant);
8829     ics_user_moved = ics_clock_paused = FALSE;
8830     ics_getting_history = H_FALSE;
8831     ics_gamenum = -1;
8832     white_holding[0] = black_holding[0] = NULLCHAR;
8833     ClearProgramStats();
8834     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8835
8836     ResetFrontEnd();
8837     ClearHighlights();
8838     flipView = appData.flipView;
8839     ClearPremoveHighlights();
8840     gotPremove = FALSE;
8841     alarmSounded = FALSE;
8842
8843     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8844     if(appData.serverMovesName != NULL) {
8845         /* [HGM] prepare to make moves file for broadcasting */
8846         clock_t t = clock();
8847         if(serverMoves != NULL) fclose(serverMoves);
8848         serverMoves = fopen(appData.serverMovesName, "r");
8849         if(serverMoves != NULL) {
8850             fclose(serverMoves);
8851             /* delay 15 sec before overwriting, so all clients can see end */
8852             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8853         }
8854         serverMoves = fopen(appData.serverMovesName, "w");
8855     }
8856
8857     ExitAnalyzeMode();
8858     gameMode = BeginningOfGame;
8859     ModeHighlight();
8860
8861     if(appData.icsActive) gameInfo.variant = VariantNormal;
8862     currentMove = forwardMostMove = backwardMostMove = 0;
8863     InitPosition(redraw);
8864     for (i = 0; i < MAX_MOVES; i++) {
8865         if (commentList[i] != NULL) {
8866             free(commentList[i]);
8867             commentList[i] = NULL;
8868         }
8869     }
8870
8871     ResetClocks();
8872     timeRemaining[0][0] = whiteTimeRemaining;
8873     timeRemaining[1][0] = blackTimeRemaining;
8874     if (first.pr == NULL) {
8875         StartChessProgram(&first);
8876     }
8877     if (init) {
8878             InitChessProgram(&first, startedFromSetupPosition);
8879     }
8880
8881     DisplayTitle("");
8882     DisplayMessage("", "");
8883     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8884     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8885     return;
8886 }
8887
8888 void
8889 AutoPlayGameLoop()
8890 {
8891     for (;;) {
8892         if (!AutoPlayOneMove())
8893           return;
8894         if (matchMode || appData.timeDelay == 0)
8895           continue;
8896         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8897           return;
8898         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8899         break;
8900     }
8901 }
8902
8903
8904 int
8905 AutoPlayOneMove()
8906 {
8907     int fromX, fromY, toX, toY;
8908
8909     if (appData.debugMode) {
8910       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8911     }
8912
8913     if (gameMode != PlayFromGameFile)
8914       return FALSE;
8915
8916     if (currentMove >= forwardMostMove) {
8917       gameMode = EditGame;
8918       ModeHighlight();
8919
8920       /* [AS] Clear current move marker at the end of a game */
8921       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8922
8923       return FALSE;
8924     }
8925
8926     toX = moveList[currentMove][2] - AAA;
8927     toY = moveList[currentMove][3] - ONE;
8928
8929     if (moveList[currentMove][1] == '@') {
8930         if (appData.highlightLastMove) {
8931             SetHighlights(-1, -1, toX, toY);
8932         }
8933     } else {
8934         fromX = moveList[currentMove][0] - AAA;
8935         fromY = moveList[currentMove][1] - ONE;
8936
8937         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8938
8939         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8940
8941         if (appData.highlightLastMove) {
8942             SetHighlights(fromX, fromY, toX, toY);
8943         }
8944     }
8945     DisplayMove(currentMove);
8946     SendMoveToProgram(currentMove++, &first);
8947     DisplayBothClocks();
8948     DrawPosition(FALSE, boards[currentMove]);
8949     // [HGM] PV info: always display, routine tests if empty
8950     DisplayComment(currentMove - 1, commentList[currentMove]);
8951     return TRUE;
8952 }
8953
8954
8955 int
8956 LoadGameOneMove(readAhead)
8957      ChessMove readAhead;
8958 {
8959     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8960     char promoChar = NULLCHAR;
8961     ChessMove moveType;
8962     char move[MSG_SIZ];
8963     char *p, *q;
8964
8965     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8966         gameMode != AnalyzeMode && gameMode != Training) {
8967         gameFileFP = NULL;
8968         return FALSE;
8969     }
8970
8971     yyboardindex = forwardMostMove;
8972     if (readAhead != (ChessMove)0) {
8973       moveType = readAhead;
8974     } else {
8975       if (gameFileFP == NULL)
8976           return FALSE;
8977       moveType = (ChessMove) yylex();
8978     }
8979
8980     done = FALSE;
8981     switch (moveType) {
8982       case Comment:
8983         if (appData.debugMode)
8984           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8985         p = yy_text;
8986
8987         /* append the comment but don't display it */
8988         AppendComment(currentMove, p, FALSE);
8989         return TRUE;
8990
8991       case WhiteCapturesEnPassant:
8992       case BlackCapturesEnPassant:
8993       case WhitePromotionChancellor:
8994       case BlackPromotionChancellor:
8995       case WhitePromotionArchbishop:
8996       case BlackPromotionArchbishop:
8997       case WhitePromotionCentaur:
8998       case BlackPromotionCentaur:
8999       case WhitePromotionQueen:
9000       case BlackPromotionQueen:
9001       case WhitePromotionRook:
9002       case BlackPromotionRook:
9003       case WhitePromotionBishop:
9004       case BlackPromotionBishop:
9005       case WhitePromotionKnight:
9006       case BlackPromotionKnight:
9007       case WhitePromotionKing:
9008       case BlackPromotionKing:
9009       case NormalMove:
9010       case WhiteKingSideCastle:
9011       case WhiteQueenSideCastle:
9012       case BlackKingSideCastle:
9013       case BlackQueenSideCastle:
9014       case WhiteKingSideCastleWild:
9015       case WhiteQueenSideCastleWild:
9016       case BlackKingSideCastleWild:
9017       case BlackQueenSideCastleWild:
9018       /* PUSH Fabien */
9019       case WhiteHSideCastleFR:
9020       case WhiteASideCastleFR:
9021       case BlackHSideCastleFR:
9022       case BlackASideCastleFR:
9023       /* POP Fabien */
9024         if (appData.debugMode)
9025           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9026         fromX = currentMoveString[0] - AAA;
9027         fromY = currentMoveString[1] - ONE;
9028         toX = currentMoveString[2] - AAA;
9029         toY = currentMoveString[3] - ONE;
9030         promoChar = currentMoveString[4];
9031         break;
9032
9033       case WhiteDrop:
9034       case BlackDrop:
9035         if (appData.debugMode)
9036           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9037         fromX = moveType == WhiteDrop ?
9038           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9039         (int) CharToPiece(ToLower(currentMoveString[0]));
9040         fromY = DROP_RANK;
9041         toX = currentMoveString[2] - AAA;
9042         toY = currentMoveString[3] - ONE;
9043         break;
9044
9045       case WhiteWins:
9046       case BlackWins:
9047       case GameIsDrawn:
9048       case GameUnfinished:
9049         if (appData.debugMode)
9050           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9051         p = strchr(yy_text, '{');
9052         if (p == NULL) p = strchr(yy_text, '(');
9053         if (p == NULL) {
9054             p = yy_text;
9055             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9056         } else {
9057             q = strchr(p, *p == '{' ? '}' : ')');
9058             if (q != NULL) *q = NULLCHAR;
9059             p++;
9060         }
9061         GameEnds(moveType, p, GE_FILE);
9062         done = TRUE;
9063         if (cmailMsgLoaded) {
9064             ClearHighlights();
9065             flipView = WhiteOnMove(currentMove);
9066             if (moveType == GameUnfinished) flipView = !flipView;
9067             if (appData.debugMode)
9068               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9069         }
9070         break;
9071
9072       case (ChessMove) 0:       /* end of file */
9073         if (appData.debugMode)
9074           fprintf(debugFP, "Parser hit end of file\n");
9075         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9076           case MT_NONE:
9077           case MT_CHECK:
9078             break;
9079           case MT_CHECKMATE:
9080           case MT_STAINMATE:
9081             if (WhiteOnMove(currentMove)) {
9082                 GameEnds(BlackWins, "Black mates", GE_FILE);
9083             } else {
9084                 GameEnds(WhiteWins, "White mates", GE_FILE);
9085             }
9086             break;
9087           case MT_STALEMATE:
9088             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9089             break;
9090         }
9091         done = TRUE;
9092         break;
9093
9094       case MoveNumberOne:
9095         if (lastLoadGameStart == GNUChessGame) {
9096             /* GNUChessGames have numbers, but they aren't move numbers */
9097             if (appData.debugMode)
9098               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9099                       yy_text, (int) moveType);
9100             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9101         }
9102         /* else fall thru */
9103
9104       case XBoardGame:
9105       case GNUChessGame:
9106       case PGNTag:
9107         /* Reached start of next game in file */
9108         if (appData.debugMode)
9109           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9110         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9111           case MT_NONE:
9112           case MT_CHECK:
9113             break;
9114           case MT_CHECKMATE:
9115           case MT_STAINMATE:
9116             if (WhiteOnMove(currentMove)) {
9117                 GameEnds(BlackWins, "Black mates", GE_FILE);
9118             } else {
9119                 GameEnds(WhiteWins, "White mates", GE_FILE);
9120             }
9121             break;
9122           case MT_STALEMATE:
9123             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9124             break;
9125         }
9126         done = TRUE;
9127         break;
9128
9129       case PositionDiagram:     /* should not happen; ignore */
9130       case ElapsedTime:         /* ignore */
9131       case NAG:                 /* ignore */
9132         if (appData.debugMode)
9133           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9134                   yy_text, (int) moveType);
9135         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9136
9137       case IllegalMove:
9138         if (appData.testLegality) {
9139             if (appData.debugMode)
9140               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9141             sprintf(move, _("Illegal move: %d.%s%s"),
9142                     (forwardMostMove / 2) + 1,
9143                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9144             DisplayError(move, 0);
9145             done = TRUE;
9146         } else {
9147             if (appData.debugMode)
9148               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9149                       yy_text, currentMoveString);
9150             fromX = currentMoveString[0] - AAA;
9151             fromY = currentMoveString[1] - ONE;
9152             toX = currentMoveString[2] - AAA;
9153             toY = currentMoveString[3] - ONE;
9154             promoChar = currentMoveString[4];
9155         }
9156         break;
9157
9158       case AmbiguousMove:
9159         if (appData.debugMode)
9160           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9161         sprintf(move, _("Ambiguous move: %d.%s%s"),
9162                 (forwardMostMove / 2) + 1,
9163                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9164         DisplayError(move, 0);
9165         done = TRUE;
9166         break;
9167
9168       default:
9169       case ImpossibleMove:
9170         if (appData.debugMode)
9171           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9172         sprintf(move, _("Illegal move: %d.%s%s"),
9173                 (forwardMostMove / 2) + 1,
9174                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9175         DisplayError(move, 0);
9176         done = TRUE;
9177         break;
9178     }
9179
9180     if (done) {
9181         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9182             DrawPosition(FALSE, boards[currentMove]);
9183             DisplayBothClocks();
9184             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9185               DisplayComment(currentMove - 1, commentList[currentMove]);
9186         }
9187         (void) StopLoadGameTimer();
9188         gameFileFP = NULL;
9189         cmailOldMove = forwardMostMove;
9190         return FALSE;
9191     } else {
9192         /* currentMoveString is set as a side-effect of yylex */
9193         strcat(currentMoveString, "\n");
9194         strcpy(moveList[forwardMostMove], currentMoveString);
9195
9196         thinkOutput[0] = NULLCHAR;
9197         MakeMove(fromX, fromY, toX, toY, promoChar);
9198         currentMove = forwardMostMove;
9199         return TRUE;
9200     }
9201 }
9202
9203 /* Load the nth game from the given file */
9204 int
9205 LoadGameFromFile(filename, n, title, useList)
9206      char *filename;
9207      int n;
9208      char *title;
9209      /*Boolean*/ int useList;
9210 {
9211     FILE *f;
9212     char buf[MSG_SIZ];
9213
9214     if (strcmp(filename, "-") == 0) {
9215         f = stdin;
9216         title = "stdin";
9217     } else {
9218         f = fopen(filename, "rb");
9219         if (f == NULL) {
9220           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9221             DisplayError(buf, errno);
9222             return FALSE;
9223         }
9224     }
9225     if (fseek(f, 0, 0) == -1) {
9226         /* f is not seekable; probably a pipe */
9227         useList = FALSE;
9228     }
9229     if (useList && n == 0) {
9230         int error = GameListBuild(f);
9231         if (error) {
9232             DisplayError(_("Cannot build game list"), error);
9233         } else if (!ListEmpty(&gameList) &&
9234                    ((ListGame *) gameList.tailPred)->number > 1) {
9235           // TODO convert to GTK
9236           //        GameListPopUp(f, title);
9237             return TRUE;
9238         }
9239         GameListDestroy();
9240         n = 1;
9241     }
9242     if (n == 0) n = 1;
9243     return LoadGame(f, n, title, FALSE);
9244 }
9245
9246
9247 void
9248 MakeRegisteredMove()
9249 {
9250     int fromX, fromY, toX, toY;
9251     char promoChar;
9252     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9253         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9254           case CMAIL_MOVE:
9255           case CMAIL_DRAW:
9256             if (appData.debugMode)
9257               fprintf(debugFP, "Restoring %s for game %d\n",
9258                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9259
9260             thinkOutput[0] = NULLCHAR;
9261             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9262             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9263             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9264             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9265             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9266             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9267             MakeMove(fromX, fromY, toX, toY, promoChar);
9268             ShowMove(fromX, fromY, toX, toY);
9269             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9270               case MT_NONE:
9271               case MT_CHECK:
9272                 break;
9273
9274               case MT_CHECKMATE:
9275               case MT_STAINMATE:
9276                 if (WhiteOnMove(currentMove)) {
9277                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9278                 } else {
9279                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9280                 }
9281                 break;
9282
9283               case MT_STALEMATE:
9284                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9285                 break;
9286             }
9287
9288             break;
9289
9290           case CMAIL_RESIGN:
9291             if (WhiteOnMove(currentMove)) {
9292                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9293             } else {
9294                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9295             }
9296             break;
9297
9298           case CMAIL_ACCEPT:
9299             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9300             break;
9301
9302           default:
9303             break;
9304         }
9305     }
9306
9307     return;
9308 }
9309
9310 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9311 int
9312 CmailLoadGame(f, gameNumber, title, useList)
9313      FILE *f;
9314      int gameNumber;
9315      char *title;
9316      int useList;
9317 {
9318     int retVal;
9319
9320     if (gameNumber > nCmailGames) {
9321         DisplayError(_("No more games in this message"), 0);
9322         return FALSE;
9323     }
9324     if (f == lastLoadGameFP) {
9325         int offset = gameNumber - lastLoadGameNumber;
9326         if (offset == 0) {
9327             cmailMsg[0] = NULLCHAR;
9328             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9329                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9330                 nCmailMovesRegistered--;
9331             }
9332             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9333             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9334                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9335             }
9336         } else {
9337             if (! RegisterMove()) return FALSE;
9338         }
9339     }
9340
9341     retVal = LoadGame(f, gameNumber, title, useList);
9342
9343     /* Make move registered during previous look at this game, if any */
9344     MakeRegisteredMove();
9345
9346     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9347         commentList[currentMove]
9348           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9349         DisplayComment(currentMove - 1, commentList[currentMove]);
9350     }
9351
9352     return retVal;
9353 }
9354
9355 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9356 int
9357 ReloadGame(offset)
9358      int offset;
9359 {
9360     int gameNumber = lastLoadGameNumber + offset;
9361     if (lastLoadGameFP == NULL) {
9362         DisplayError(_("No game has been loaded yet"), 0);
9363         return FALSE;
9364     }
9365     if (gameNumber <= 0) {
9366         DisplayError(_("Can't back up any further"), 0);
9367         return FALSE;
9368     }
9369     if (cmailMsgLoaded) {
9370         return CmailLoadGame(lastLoadGameFP, gameNumber,
9371                              lastLoadGameTitle, lastLoadGameUseList);
9372     } else {
9373         return LoadGame(lastLoadGameFP, gameNumber,
9374                         lastLoadGameTitle, lastLoadGameUseList);
9375     }
9376 }
9377
9378
9379
9380 /* Load the nth game from open file f */
9381 int
9382 LoadGame(f, gameNumber, title, useList)
9383      FILE *f;
9384      int gameNumber;
9385      char *title;
9386      int useList;
9387 {
9388     ChessMove cm;
9389     char buf[MSG_SIZ];
9390     int gn = gameNumber;
9391     ListGame *lg = NULL;
9392     int numPGNTags = 0;
9393     int err;
9394     GameMode oldGameMode;
9395     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9396
9397     if (appData.debugMode)
9398         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9399
9400     if (gameMode == Training )
9401         SetTrainingModeOff();
9402
9403     oldGameMode = gameMode;
9404     if (gameMode != BeginningOfGame) 
9405       {
9406         Reset(FALSE, TRUE);
9407       };
9408
9409     gameFileFP = f;
9410     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9411       {
9412         fclose(lastLoadGameFP);
9413       };
9414
9415     if (useList) 
9416       {
9417         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9418         
9419         if (lg) 
9420           {
9421             fseek(f, lg->offset, 0);
9422             GameListHighlight(gameNumber);
9423             gn = 1;
9424           }
9425         else 
9426           {
9427             DisplayError(_("Game number out of range"), 0);
9428             return FALSE;
9429           };
9430       } 
9431     else 
9432       {
9433         GameListDestroy();
9434         if (fseek(f, 0, 0) == -1) 
9435           {
9436             if (f == lastLoadGameFP ?
9437                 gameNumber == lastLoadGameNumber + 1 :
9438                 gameNumber == 1) 
9439               {
9440                 gn = 1;
9441               } 
9442             else 
9443               {
9444                 DisplayError(_("Can't seek on game file"), 0);
9445                 return FALSE;
9446               };
9447           };
9448       };
9449
9450     lastLoadGameFP      = f;
9451     lastLoadGameNumber  = gameNumber;
9452     strcpy(lastLoadGameTitle, title);
9453     lastLoadGameUseList = useList;
9454
9455     yynewfile(f);
9456
9457     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9458       {
9459         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9460                  lg->gameInfo.black);
9461         DisplayTitle(buf);
9462       } 
9463     else if (*title != NULLCHAR) 
9464       {
9465         if (gameNumber > 1) 
9466           {
9467             sprintf(buf, "%s %d", title, gameNumber);
9468             DisplayTitle(buf);
9469           } 
9470         else 
9471           {
9472             DisplayTitle(title);
9473           };
9474       };
9475
9476     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9477       {
9478         gameMode = PlayFromGameFile;
9479         ModeHighlight();
9480       };
9481
9482     currentMove = forwardMostMove = backwardMostMove = 0;
9483     CopyBoard(boards[0], initialPosition);
9484     StopClocks();
9485
9486     /*
9487      * Skip the first gn-1 games in the file.
9488      * Also skip over anything that precedes an identifiable
9489      * start of game marker, to avoid being confused by
9490      * garbage at the start of the file.  Currently
9491      * recognized start of game markers are the move number "1",
9492      * the pattern "gnuchess .* game", the pattern
9493      * "^[#;%] [^ ]* game file", and a PGN tag block.
9494      * A game that starts with one of the latter two patterns
9495      * will also have a move number 1, possibly
9496      * following a position diagram.
9497      * 5-4-02: Let's try being more lenient and allowing a game to
9498      * start with an unnumbered move.  Does that break anything?
9499      */
9500     cm = lastLoadGameStart = (ChessMove) 0;
9501     while (gn > 0) {
9502         yyboardindex = forwardMostMove;
9503         cm = (ChessMove) yylex();
9504         switch (cm) {
9505           case (ChessMove) 0:
9506             if (cmailMsgLoaded) {
9507                 nCmailGames = CMAIL_MAX_GAMES - gn;
9508             } else {
9509                 Reset(TRUE, TRUE);
9510                 DisplayError(_("Game not found in file"), 0);
9511             }
9512             return FALSE;
9513
9514           case GNUChessGame:
9515           case XBoardGame:
9516             gn--;
9517             lastLoadGameStart = cm;
9518             break;
9519
9520           case MoveNumberOne:
9521             switch (lastLoadGameStart) {
9522               case GNUChessGame:
9523               case XBoardGame:
9524               case PGNTag:
9525                 break;
9526               case MoveNumberOne:
9527               case (ChessMove) 0:
9528                 gn--;           /* count this game */
9529                 lastLoadGameStart = cm;
9530                 break;
9531               default:
9532                 /* impossible */
9533                 break;
9534             }
9535             break;
9536
9537           case PGNTag:
9538             switch (lastLoadGameStart) {
9539               case GNUChessGame:
9540               case PGNTag:
9541               case MoveNumberOne:
9542               case (ChessMove) 0:
9543                 gn--;           /* count this game */
9544                 lastLoadGameStart = cm;
9545                 break;
9546               case XBoardGame:
9547                 lastLoadGameStart = cm; /* game counted already */
9548                 break;
9549               default:
9550                 /* impossible */
9551                 break;
9552             }
9553             if (gn > 0) {
9554                 do {
9555                     yyboardindex = forwardMostMove;
9556                     cm = (ChessMove) yylex();
9557                 } while (cm == PGNTag || cm == Comment);
9558             }
9559             break;
9560
9561           case WhiteWins:
9562           case BlackWins:
9563           case GameIsDrawn:
9564             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9565                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9566                     != CMAIL_OLD_RESULT) {
9567                     nCmailResults ++ ;
9568                     cmailResult[  CMAIL_MAX_GAMES
9569                                 - gn - 1] = CMAIL_OLD_RESULT;
9570                 }
9571             }
9572             break;
9573
9574           case NormalMove:
9575             /* Only a NormalMove can be at the start of a game
9576              * without a position diagram. */
9577             if (lastLoadGameStart == (ChessMove) 0) {
9578               gn--;
9579               lastLoadGameStart = MoveNumberOne;
9580             }
9581             break;
9582
9583           default:
9584             break;
9585         }
9586     }
9587
9588     if (appData.debugMode)
9589       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9590
9591     if (cm == XBoardGame) {
9592         /* Skip any header junk before position diagram and/or move 1 */
9593         for (;;) {
9594             yyboardindex = forwardMostMove;
9595             cm = (ChessMove) yylex();
9596
9597             if (cm == (ChessMove) 0 ||
9598                 cm == GNUChessGame || cm == XBoardGame) {
9599                 /* Empty game; pretend end-of-file and handle later */
9600                 cm = (ChessMove) 0;
9601                 break;
9602             }
9603
9604             if (cm == MoveNumberOne || cm == PositionDiagram ||
9605                 cm == PGNTag || cm == Comment)
9606               break;
9607         }
9608     } else if (cm == GNUChessGame) {
9609         if (gameInfo.event != NULL) {
9610             free(gameInfo.event);
9611         }
9612         gameInfo.event = StrSave(yy_text);
9613     }
9614
9615     startedFromSetupPosition = FALSE;
9616     while (cm == PGNTag) {
9617         if (appData.debugMode)
9618           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9619         err = ParsePGNTag(yy_text, &gameInfo);
9620         if (!err) numPGNTags++;
9621
9622         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9623         if(gameInfo.variant != oldVariant) {
9624             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9625             InitPosition(TRUE);
9626             oldVariant = gameInfo.variant;
9627             if (appData.debugMode)
9628               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9629         }
9630
9631
9632         if (gameInfo.fen != NULL) {
9633           Board initial_position;
9634           startedFromSetupPosition = TRUE;
9635           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9636             Reset(TRUE, TRUE);
9637             DisplayError(_("Bad FEN position in file"), 0);
9638             return FALSE;
9639           }
9640           CopyBoard(boards[0], initial_position);
9641           if (blackPlaysFirst) {
9642             currentMove = forwardMostMove = backwardMostMove = 1;
9643             CopyBoard(boards[1], initial_position);
9644             strcpy(moveList[0], "");
9645             strcpy(parseList[0], "");
9646             timeRemaining[0][1] = whiteTimeRemaining;
9647             timeRemaining[1][1] = blackTimeRemaining;
9648             if (commentList[0] != NULL) {
9649               commentList[1] = commentList[0];
9650               commentList[0] = NULL;
9651             }
9652           } else {
9653             currentMove = forwardMostMove = backwardMostMove = 0;
9654           }
9655           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9656           {   int i;
9657               initialRulePlies = FENrulePlies;
9658               for( i=0; i< nrCastlingRights; i++ )
9659                   initialRights[i] = initial_position[CASTLING][i];
9660           }
9661           yyboardindex = forwardMostMove;
9662           free(gameInfo.fen);
9663           gameInfo.fen = NULL;
9664         }
9665
9666         yyboardindex = forwardMostMove;
9667         cm = (ChessMove) yylex();
9668
9669         /* Handle comments interspersed among the tags */
9670         while (cm == Comment) {
9671             char *p;
9672             if (appData.debugMode)
9673               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9674             p = yy_text;
9675             AppendComment(currentMove, p, FALSE);
9676             yyboardindex = forwardMostMove;
9677             cm = (ChessMove) yylex();
9678         }
9679     }
9680
9681     /* don't rely on existence of Event tag since if game was
9682      * pasted from clipboard the Event tag may not exist
9683      */
9684     if (numPGNTags > 0){
9685         char *tags;
9686         if (gameInfo.variant == VariantNormal) {
9687           gameInfo.variant = StringToVariant(gameInfo.event);
9688         }
9689         if (!matchMode) {
9690           if( appData.autoDisplayTags ) {
9691             tags = PGNTags(&gameInfo);
9692             TagsPopUp(tags, CmailMsg());
9693             free(tags);
9694           }
9695         }
9696     } else {
9697         /* Make something up, but don't display it now */
9698         SetGameInfo();
9699         TagsPopDown();
9700     }
9701
9702     if (cm == PositionDiagram) {
9703         int i, j;
9704         char *p;
9705         Board initial_position;
9706
9707         if (appData.debugMode)
9708           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9709
9710         if (!startedFromSetupPosition) {
9711             p = yy_text;
9712             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9713               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9714                 switch (*p) {
9715                   case '[':
9716                   case '-':
9717                   case ' ':
9718                   case '\t':
9719                   case '\n':
9720                   case '\r':
9721                     break;
9722                   default:
9723                     initial_position[i][j++] = CharToPiece(*p);
9724                     break;
9725                 }
9726             while (*p == ' ' || *p == '\t' ||
9727                    *p == '\n' || *p == '\r') p++;
9728
9729             if (strncmp(p, "black", strlen("black"))==0)
9730               blackPlaysFirst = TRUE;
9731             else
9732               blackPlaysFirst = FALSE;
9733             startedFromSetupPosition = TRUE;
9734
9735             CopyBoard(boards[0], initial_position);
9736             if (blackPlaysFirst) {
9737                 currentMove = forwardMostMove = backwardMostMove = 1;
9738                 CopyBoard(boards[1], initial_position);
9739                 strcpy(moveList[0], "");
9740                 strcpy(parseList[0], "");
9741                 timeRemaining[0][1] = whiteTimeRemaining;
9742                 timeRemaining[1][1] = blackTimeRemaining;
9743                 if (commentList[0] != NULL) {
9744                     commentList[1] = commentList[0];
9745                     commentList[0] = NULL;
9746                 }
9747             } else {
9748                 currentMove = forwardMostMove = backwardMostMove = 0;
9749             }
9750         }
9751         yyboardindex = forwardMostMove;
9752         cm = (ChessMove) yylex();
9753     }
9754
9755     if (first.pr == NoProc) {
9756         StartChessProgram(&first);
9757     }
9758     InitChessProgram(&first, FALSE);
9759     SendToProgram("force\n", &first);
9760     if (startedFromSetupPosition) {
9761         SendBoard(&first, forwardMostMove);
9762     if (appData.debugMode) {
9763         fprintf(debugFP, "Load Game\n");
9764     }
9765         DisplayBothClocks();
9766     }
9767
9768     /* [HGM] server: flag to write setup moves in broadcast file as one */
9769     loadFlag = appData.suppressLoadMoves;
9770
9771     while (cm == Comment) {
9772         char *p;
9773         if (appData.debugMode)
9774           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9775         p = yy_text;
9776         AppendComment(currentMove, p, FALSE);
9777         yyboardindex = forwardMostMove;
9778         cm = (ChessMove) yylex();
9779     }
9780
9781     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9782         cm == WhiteWins || cm == BlackWins ||
9783         cm == GameIsDrawn || cm == GameUnfinished) {
9784         DisplayMessage("", _("No moves in game"));
9785         if (cmailMsgLoaded) {
9786             if (appData.debugMode)
9787               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9788             ClearHighlights();
9789             flipView = FALSE;
9790         }
9791         DrawPosition(FALSE, boards[currentMove]);
9792         DisplayBothClocks();
9793         gameMode = EditGame;
9794         ModeHighlight();
9795         gameFileFP = NULL;
9796         cmailOldMove = 0;
9797         return TRUE;
9798     }
9799
9800     // [HGM] PV info: routine tests if comment empty
9801     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9802         DisplayComment(currentMove - 1, commentList[currentMove]);
9803     }
9804     if (!matchMode && appData.timeDelay != 0)
9805       DrawPosition(FALSE, boards[currentMove]);
9806
9807     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9808       programStats.ok_to_send = 1;
9809     }
9810
9811     /* if the first token after the PGN tags is a move
9812      * and not move number 1, retrieve it from the parser
9813      */
9814     if (cm != MoveNumberOne)
9815         LoadGameOneMove(cm);
9816
9817     /* load the remaining moves from the file */
9818     while (LoadGameOneMove((ChessMove)0)) {
9819       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9820       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9821     }
9822
9823     /* rewind to the start of the game */
9824     currentMove = backwardMostMove;
9825
9826     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9827
9828     if (oldGameMode == AnalyzeFile ||
9829         oldGameMode == AnalyzeMode) {
9830       AnalyzeFileEvent();
9831     }
9832
9833     if (matchMode || appData.timeDelay == 0) {
9834       ToEndEvent();
9835       gameMode = EditGame;
9836       ModeHighlight();
9837     } else if (appData.timeDelay > 0) {
9838       AutoPlayGameLoop();
9839     }
9840
9841     if (appData.debugMode)
9842         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9843
9844     loadFlag = 0; /* [HGM] true game starts */
9845     return TRUE;
9846 }
9847
9848 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9849 int
9850 ReloadPosition(offset)
9851      int offset;
9852 {
9853     int positionNumber = lastLoadPositionNumber + offset;
9854     if (lastLoadPositionFP == NULL) {
9855         DisplayError(_("No position has been loaded yet"), 0);
9856         return FALSE;
9857     }
9858     if (positionNumber <= 0) {
9859         DisplayError(_("Can't back up any further"), 0);
9860         return FALSE;
9861     }
9862     return LoadPosition(lastLoadPositionFP, positionNumber,
9863                         lastLoadPositionTitle);
9864 }
9865
9866 /* Load the nth position from the given file */
9867 int
9868 LoadPositionFromFile(filename, n, title)
9869      char *filename;
9870      int n;
9871      char *title;
9872 {
9873     FILE *f;
9874     char buf[MSG_SIZ];
9875
9876     if (strcmp(filename, "-") == 0) {
9877         return LoadPosition(stdin, n, "stdin");
9878     } else {
9879         f = fopen(filename, "rb");
9880         if (f == NULL) {
9881             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9882             DisplayError(buf, errno);
9883             return FALSE;
9884         } else {
9885             return LoadPosition(f, n, title);
9886         }
9887     }
9888 }
9889
9890 /* Load the nth position from the given open file, and close it */
9891 int
9892 LoadPosition(f, positionNumber, title)
9893      FILE *f;
9894      int positionNumber;
9895      char *title;
9896 {
9897     char *p, line[MSG_SIZ];
9898     Board initial_position;
9899     int i, j, fenMode, pn;
9900
9901     if (gameMode == Training )
9902         SetTrainingModeOff();
9903
9904     if (gameMode != BeginningOfGame) {
9905         Reset(FALSE, TRUE);
9906     }
9907     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9908         fclose(lastLoadPositionFP);
9909     }
9910     if (positionNumber == 0) positionNumber = 1;
9911     lastLoadPositionFP = f;
9912     lastLoadPositionNumber = positionNumber;
9913     strcpy(lastLoadPositionTitle, title);
9914     if (first.pr == NoProc) {
9915       StartChessProgram(&first);
9916       InitChessProgram(&first, FALSE);
9917     }
9918     pn = positionNumber;
9919     if (positionNumber < 0) {
9920         /* Negative position number means to seek to that byte offset */
9921         if (fseek(f, -positionNumber, 0) == -1) {
9922             DisplayError(_("Can't seek on position file"), 0);
9923             return FALSE;
9924         };
9925         pn = 1;
9926     } else {
9927         if (fseek(f, 0, 0) == -1) {
9928             if (f == lastLoadPositionFP ?
9929                 positionNumber == lastLoadPositionNumber + 1 :
9930                 positionNumber == 1) {
9931                 pn = 1;
9932             } else {
9933                 DisplayError(_("Can't seek on position file"), 0);
9934                 return FALSE;
9935             }
9936         }
9937     }
9938     /* See if this file is FEN or old-style xboard */
9939     if (fgets(line, MSG_SIZ, f) == NULL) {
9940         DisplayError(_("Position not found in file"), 0);
9941         return FALSE;
9942     }
9943     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9944     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9945
9946     if (pn >= 2) {
9947         if (fenMode || line[0] == '#') pn--;
9948         while (pn > 0) {
9949             /* skip positions before number pn */
9950             if (fgets(line, MSG_SIZ, f) == NULL) {
9951                 Reset(TRUE, TRUE);
9952                 DisplayError(_("Position not found in file"), 0);
9953                 return FALSE;
9954             }
9955             if (fenMode || line[0] == '#') pn--;
9956         }
9957     }
9958
9959     if (fenMode) {
9960         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9961             DisplayError(_("Bad FEN position in file"), 0);
9962             return FALSE;
9963         }
9964     } else {
9965         (void) fgets(line, MSG_SIZ, f);
9966         (void) fgets(line, MSG_SIZ, f);
9967
9968         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9969             (void) fgets(line, MSG_SIZ, f);
9970             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9971                 if (*p == ' ')
9972                   continue;
9973                 initial_position[i][j++] = CharToPiece(*p);
9974             }
9975         }
9976
9977         blackPlaysFirst = FALSE;
9978         if (!feof(f)) {
9979             (void) fgets(line, MSG_SIZ, f);
9980             if (strncmp(line, "black", strlen("black"))==0)
9981               blackPlaysFirst = TRUE;
9982         }
9983     }
9984     startedFromSetupPosition = TRUE;
9985
9986     SendToProgram("force\n", &first);
9987     CopyBoard(boards[0], initial_position);
9988     if (blackPlaysFirst) {
9989         currentMove = forwardMostMove = backwardMostMove = 1;
9990         strcpy(moveList[0], "");
9991         strcpy(parseList[0], "");
9992         CopyBoard(boards[1], initial_position);
9993         DisplayMessage("", _("Black to play"));
9994     } else {
9995         currentMove = forwardMostMove = backwardMostMove = 0;
9996         DisplayMessage("", _("White to play"));
9997     }
9998     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9999     SendBoard(&first, forwardMostMove);
10000     if (appData.debugMode) {
10001 int i, j;
10002   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10003   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10004         fprintf(debugFP, "Load Position\n");
10005     }
10006
10007     if (positionNumber > 1) {
10008         sprintf(line, "%s %d", title, positionNumber);
10009         DisplayTitle(line);
10010     } else {
10011         DisplayTitle(title);
10012     }
10013     gameMode = EditGame;
10014     ModeHighlight();
10015     ResetClocks();
10016     timeRemaining[0][1] = whiteTimeRemaining;
10017     timeRemaining[1][1] = blackTimeRemaining;
10018     DrawPosition(FALSE, boards[currentMove]);
10019
10020     return TRUE;
10021 }
10022
10023
10024 void
10025 CopyPlayerNameIntoFileName(dest, src)
10026      char **dest, *src;
10027 {
10028     while (*src != NULLCHAR && *src != ',') {
10029         if (*src == ' ') {
10030             *(*dest)++ = '_';
10031             src++;
10032         } else {
10033             *(*dest)++ = *src++;
10034         }
10035     }
10036 }
10037
10038 char *DefaultFileName(ext)
10039      char *ext;
10040 {
10041     static char def[MSG_SIZ];
10042     char *p;
10043
10044     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10045         p = def;
10046         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10047         *p++ = '-';
10048         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10049         *p++ = '.';
10050         strcpy(p, ext);
10051     } else {
10052         def[0] = NULLCHAR;
10053     }
10054     return def;
10055 }
10056
10057 /* Save the current game to the given file */
10058 int
10059 SaveGameToFile(filename, append)
10060      char *filename;
10061      int append;
10062 {
10063     FILE *f;
10064     char buf[MSG_SIZ];
10065
10066     if (strcmp(filename, "-") == 0) {
10067         return SaveGame(stdout, 0, NULL);
10068     } else {
10069         f = fopen(filename, append ? "a" : "w");
10070         if (f == NULL) {
10071             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10072             DisplayError(buf, errno);
10073             return FALSE;
10074         } else {
10075             return SaveGame(f, 0, NULL);
10076         }
10077     }
10078 }
10079
10080 char *
10081 SavePart(str)
10082      char *str;
10083 {
10084     static char buf[MSG_SIZ];
10085     char *p;
10086
10087     p = strchr(str, ' ');
10088     if (p == NULL) return str;
10089     strncpy(buf, str, p - str);
10090     buf[p - str] = NULLCHAR;
10091     return buf;
10092 }
10093
10094 #define PGN_MAX_LINE 75
10095
10096 #define PGN_SIDE_WHITE  0
10097 #define PGN_SIDE_BLACK  1
10098
10099 /* [AS] */
10100 static int FindFirstMoveOutOfBook( int side )
10101 {
10102     int result = -1;
10103
10104     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10105         int index = backwardMostMove;
10106         int has_book_hit = 0;
10107
10108         if( (index % 2) != side ) {
10109             index++;
10110         }
10111
10112         while( index < forwardMostMove ) {
10113             /* Check to see if engine is in book */
10114             int depth = pvInfoList[index].depth;
10115             int score = pvInfoList[index].score;
10116             int in_book = 0;
10117
10118             if( depth <= 2 ) {
10119                 in_book = 1;
10120             }
10121             else if( score == 0 && depth == 63 ) {
10122                 in_book = 1; /* Zappa */
10123             }
10124             else if( score == 2 && depth == 99 ) {
10125                 in_book = 1; /* Abrok */
10126             }
10127
10128             has_book_hit += in_book;
10129
10130             if( ! in_book ) {
10131                 result = index;
10132
10133                 break;
10134             }
10135
10136             index += 2;
10137         }
10138     }
10139
10140     return result;
10141 }
10142
10143 /* [AS] */
10144 void GetOutOfBookInfo( char * buf )
10145 {
10146     int oob[2];
10147     int i;
10148     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10149
10150     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10151     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10152
10153     *buf = '\0';
10154
10155     if( oob[0] >= 0 || oob[1] >= 0 ) {
10156         for( i=0; i<2; i++ ) {
10157             int idx = oob[i];
10158
10159             if( idx >= 0 ) {
10160                 if( i > 0 && oob[0] >= 0 ) {
10161                     strcat( buf, "   " );
10162                 }
10163
10164                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10165                 sprintf( buf+strlen(buf), "%s%.2f",
10166                     pvInfoList[idx].score >= 0 ? "+" : "",
10167                     pvInfoList[idx].score / 100.0 );
10168             }
10169         }
10170     }
10171 }
10172
10173 /* Save game in PGN style and close the file */
10174 int
10175 SaveGamePGN(f)
10176      FILE *f;
10177 {
10178     int i, offset, linelen, newblock;
10179     time_t tm;
10180 //    char *movetext;
10181     char numtext[32];
10182     int movelen, numlen, blank;
10183     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10184
10185     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10186
10187     tm = time((time_t *) NULL);
10188
10189     PrintPGNTags(f, &gameInfo);
10190
10191     if (backwardMostMove > 0 || startedFromSetupPosition) {
10192         char *fen = PositionToFEN(backwardMostMove, NULL);
10193         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10194         fprintf(f, "\n{--------------\n");
10195         PrintPosition(f, backwardMostMove);
10196         fprintf(f, "--------------}\n");
10197         free(fen);
10198     }
10199     else {
10200         /* [AS] Out of book annotation */
10201         if( appData.saveOutOfBookInfo ) {
10202             char buf[64];
10203
10204             GetOutOfBookInfo( buf );
10205
10206             if( buf[0] != '\0' ) {
10207                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10208             }
10209         }
10210
10211         fprintf(f, "\n");
10212     }
10213
10214     i = backwardMostMove;
10215     linelen = 0;
10216     newblock = TRUE;
10217
10218     while (i < forwardMostMove) {
10219         /* Print comments preceding this move */
10220         if (commentList[i] != NULL) {
10221             if (linelen > 0) fprintf(f, "\n");
10222             fprintf(f, "%s", commentList[i]);
10223             linelen = 0;
10224             newblock = TRUE;
10225         }
10226
10227         /* Format move number */
10228         if ((i % 2) == 0) {
10229             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10230         } else {
10231             if (newblock) {
10232                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10233             } else {
10234                 numtext[0] = NULLCHAR;
10235             }
10236         }
10237         numlen = strlen(numtext);
10238         newblock = FALSE;
10239
10240         /* Print move number */
10241         blank = linelen > 0 && numlen > 0;
10242         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10243             fprintf(f, "\n");
10244             linelen = 0;
10245             blank = 0;
10246         }
10247         if (blank) {
10248             fprintf(f, " ");
10249             linelen++;
10250         }
10251         fprintf(f, "%s", numtext);
10252         linelen += numlen;
10253
10254         /* Get move */
10255         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10256         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10257
10258         /* Print move */
10259         blank = linelen > 0 && movelen > 0;
10260         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10261             fprintf(f, "\n");
10262             linelen = 0;
10263             blank = 0;
10264         }
10265         if (blank) {
10266             fprintf(f, " ");
10267             linelen++;
10268         }
10269         fprintf(f, "%s", move_buffer);
10270         linelen += movelen;
10271
10272         /* [AS] Add PV info if present */
10273         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10274             /* [HGM] add time */
10275             char buf[MSG_SIZ]; int seconds;
10276
10277             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10278
10279             if( seconds <= 0) buf[0] = 0; else
10280             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10281                 seconds = (seconds + 4)/10; // round to full seconds
10282                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10283                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10284             }
10285
10286             sprintf( move_buffer, "{%s%.2f/%d%s}",
10287                 pvInfoList[i].score >= 0 ? "+" : "",
10288                 pvInfoList[i].score / 100.0,
10289                 pvInfoList[i].depth,
10290                 buf );
10291
10292             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10293
10294             /* Print score/depth */
10295             blank = linelen > 0 && movelen > 0;
10296             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10297                 fprintf(f, "\n");
10298                 linelen = 0;
10299                 blank = 0;
10300             }
10301             if (blank) {
10302                 fprintf(f, " ");
10303                 linelen++;
10304             }
10305             fprintf(f, "%s", move_buffer);
10306             linelen += movelen;
10307         }
10308
10309         i++;
10310     }
10311
10312     /* Start a new line */
10313     if (linelen > 0) fprintf(f, "\n");
10314
10315     /* Print comments after last move */
10316     if (commentList[i] != NULL) {
10317         fprintf(f, "%s\n", commentList[i]);
10318     }
10319
10320     /* Print result */
10321     if (gameInfo.resultDetails != NULL &&
10322         gameInfo.resultDetails[0] != NULLCHAR) {
10323         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10324                 PGNResult(gameInfo.result));
10325     } else {
10326         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10327     }
10328
10329     fclose(f);
10330     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10331     return TRUE;
10332 }
10333
10334 /* Save game in old style and close the file */
10335 int
10336 SaveGameOldStyle(f)
10337      FILE *f;
10338 {
10339     int i, offset;
10340     time_t tm;
10341
10342     tm = time((time_t *) NULL);
10343
10344     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10345     PrintOpponents(f);
10346
10347     if (backwardMostMove > 0 || startedFromSetupPosition) {
10348         fprintf(f, "\n[--------------\n");
10349         PrintPosition(f, backwardMostMove);
10350         fprintf(f, "--------------]\n");
10351     } else {
10352         fprintf(f, "\n");
10353     }
10354
10355     i = backwardMostMove;
10356     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10357
10358     while (i < forwardMostMove) {
10359         if (commentList[i] != NULL) {
10360             fprintf(f, "[%s]\n", commentList[i]);
10361         }
10362
10363         if ((i % 2) == 1) {
10364             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10365             i++;
10366         } else {
10367             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10368             i++;
10369             if (commentList[i] != NULL) {
10370                 fprintf(f, "\n");
10371                 continue;
10372             }
10373             if (i >= forwardMostMove) {
10374                 fprintf(f, "\n");
10375                 break;
10376             }
10377             fprintf(f, "%s\n", parseList[i]);
10378             i++;
10379         }
10380     }
10381
10382     if (commentList[i] != NULL) {
10383         fprintf(f, "[%s]\n", commentList[i]);
10384     }
10385
10386     /* This isn't really the old style, but it's close enough */
10387     if (gameInfo.resultDetails != NULL &&
10388         gameInfo.resultDetails[0] != NULLCHAR) {
10389         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10390                 gameInfo.resultDetails);
10391     } else {
10392         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10393     }
10394
10395     fclose(f);
10396     return TRUE;
10397 }
10398
10399 /* Save the current game to open file f and close the file */
10400 int
10401 SaveGame(f, dummy, dummy2)
10402      FILE *f;
10403      int dummy;
10404      char *dummy2;
10405 {
10406     if (gameMode == EditPosition) EditPositionDone(TRUE);
10407     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10408     if (appData.oldSaveStyle)
10409       return SaveGameOldStyle(f);
10410     else
10411       return SaveGamePGN(f);
10412 }
10413
10414 /* Save the current position to the given file */
10415 int
10416 SavePositionToFile(filename)
10417      char *filename;
10418 {
10419     FILE *f;
10420     char buf[MSG_SIZ];
10421
10422     if (strcmp(filename, "-") == 0) {
10423         return SavePosition(stdout, 0, NULL);
10424     } else {
10425         f = fopen(filename, "a");
10426         if (f == NULL) {
10427             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10428             DisplayError(buf, errno);
10429             return FALSE;
10430         } else {
10431             SavePosition(f, 0, NULL);
10432             return TRUE;
10433         }
10434     }
10435 }
10436
10437 /* Save the current position to the given open file and close the file */
10438 int
10439 SavePosition(f, dummy, dummy2)
10440      FILE *f;
10441      int dummy;
10442      char *dummy2;
10443 {
10444     time_t tm;
10445     char *fen;
10446     if (gameMode == EditPosition) EditPositionDone(TRUE);
10447     if (appData.oldSaveStyle) {
10448         tm = time((time_t *) NULL);
10449
10450         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10451         PrintOpponents(f);
10452         fprintf(f, "[--------------\n");
10453         PrintPosition(f, currentMove);
10454         fprintf(f, "--------------]\n");
10455     } else {
10456         fen = PositionToFEN(currentMove, NULL);
10457         fprintf(f, "%s\n", fen);
10458         free(fen);
10459     }
10460     fclose(f);
10461     return TRUE;
10462 }
10463
10464 void
10465 ReloadCmailMsgEvent(unregister)
10466      int unregister;
10467 {
10468 #if !WIN32
10469     static char *inFilename = NULL;
10470     static char *outFilename;
10471     int i;
10472     struct stat inbuf, outbuf;
10473     int status;
10474
10475     /* Any registered moves are unregistered if unregister is set, */
10476     /* i.e. invoked by the signal handler */
10477     if (unregister) {
10478         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10479             cmailMoveRegistered[i] = FALSE;
10480             if (cmailCommentList[i] != NULL) {
10481                 free(cmailCommentList[i]);
10482                 cmailCommentList[i] = NULL;
10483             }
10484         }
10485         nCmailMovesRegistered = 0;
10486     }
10487
10488     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10489         cmailResult[i] = CMAIL_NOT_RESULT;
10490     }
10491     nCmailResults = 0;
10492
10493     if (inFilename == NULL) {
10494         /* Because the filenames are static they only get malloced once  */
10495         /* and they never get freed                                      */
10496         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10497         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10498
10499         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10500         sprintf(outFilename, "%s.out", appData.cmailGameName);
10501     }
10502
10503     status = stat(outFilename, &outbuf);
10504     if (status < 0) {
10505         cmailMailedMove = FALSE;
10506     } else {
10507         status = stat(inFilename, &inbuf);
10508         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10509     }
10510
10511     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10512        counts the games, notes how each one terminated, etc.
10513
10514        It would be nice to remove this kludge and instead gather all
10515        the information while building the game list.  (And to keep it
10516        in the game list nodes instead of having a bunch of fixed-size
10517        parallel arrays.)  Note this will require getting each game's
10518        termination from the PGN tags, as the game list builder does
10519        not process the game moves.  --mann
10520        */
10521     cmailMsgLoaded = TRUE;
10522     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10523
10524     /* Load first game in the file or popup game menu */
10525     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10526
10527 #endif /* !WIN32 */
10528     return;
10529 }
10530
10531 int
10532 RegisterMove()
10533 {
10534     FILE *f;
10535     char string[MSG_SIZ];
10536
10537     if (   cmailMailedMove
10538         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10539         return TRUE;            /* Allow free viewing  */
10540     }
10541
10542     /* Unregister move to ensure that we don't leave RegisterMove        */
10543     /* with the move registered when the conditions for registering no   */
10544     /* longer hold                                                       */
10545     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10546         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10547         nCmailMovesRegistered --;
10548
10549         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10550           {
10551               free(cmailCommentList[lastLoadGameNumber - 1]);
10552               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10553           }
10554     }
10555
10556     if (cmailOldMove == -1) {
10557         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10558         return FALSE;
10559     }
10560
10561     if (currentMove > cmailOldMove + 1) {
10562         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10563         return FALSE;
10564     }
10565
10566     if (currentMove < cmailOldMove) {
10567         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10568         return FALSE;
10569     }
10570
10571     if (forwardMostMove > currentMove) {
10572         /* Silently truncate extra moves */
10573         TruncateGame();
10574     }
10575
10576     if (   (currentMove == cmailOldMove + 1)
10577         || (   (currentMove == cmailOldMove)
10578             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10579                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10580         if (gameInfo.result != GameUnfinished) {
10581             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10582         }
10583
10584         if (commentList[currentMove] != NULL) {
10585             cmailCommentList[lastLoadGameNumber - 1]
10586               = StrSave(commentList[currentMove]);
10587         }
10588         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10589
10590         if (appData.debugMode)
10591           fprintf(debugFP, "Saving %s for game %d\n",
10592                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10593
10594         sprintf(string,
10595                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10596
10597         f = fopen(string, "w");
10598         if (appData.oldSaveStyle) {
10599             SaveGameOldStyle(f); /* also closes the file */
10600
10601             sprintf(string, "%s.pos.out", appData.cmailGameName);
10602             f = fopen(string, "w");
10603             SavePosition(f, 0, NULL); /* also closes the file */
10604         } else {
10605             fprintf(f, "{--------------\n");
10606             PrintPosition(f, currentMove);
10607             fprintf(f, "--------------}\n\n");
10608
10609             SaveGame(f, 0, NULL); /* also closes the file*/
10610         }
10611
10612         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10613         nCmailMovesRegistered ++;
10614     } else if (nCmailGames == 1) {
10615         DisplayError(_("You have not made a move yet"), 0);
10616         return FALSE;
10617     }
10618
10619     return TRUE;
10620 }
10621
10622 void
10623 MailMoveEvent()
10624 {
10625 #if !WIN32
10626     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10627     FILE *commandOutput;
10628     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10629     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10630     int nBuffers;
10631     int i;
10632     int archived;
10633     char *arcDir;
10634
10635     if (! cmailMsgLoaded) {
10636         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10637         return;
10638     }
10639
10640     if (nCmailGames == nCmailResults) {
10641         DisplayError(_("No unfinished games"), 0);
10642         return;
10643     }
10644
10645 #if CMAIL_PROHIBIT_REMAIL
10646     if (cmailMailedMove) {
10647         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);
10648         DisplayError(msg, 0);
10649         return;
10650     }
10651 #endif
10652
10653     if (! (cmailMailedMove || RegisterMove())) return;
10654
10655     if (   cmailMailedMove
10656         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10657         sprintf(string, partCommandString,
10658                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10659         commandOutput = popen(string, "r");
10660
10661         if (commandOutput == NULL) {
10662             DisplayError(_("Failed to invoke cmail"), 0);
10663         } else {
10664             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10665                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10666             }
10667             if (nBuffers > 1) {
10668                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10669                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10670                 nBytes = MSG_SIZ - 1;
10671             } else {
10672                 (void) memcpy(msg, buffer, nBytes);
10673             }
10674             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10675
10676             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10677                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10678
10679                 archived = TRUE;
10680                 for (i = 0; i < nCmailGames; i ++) {
10681                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10682                         archived = FALSE;
10683                     }
10684                 }
10685                 if (   archived
10686                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10687                         != NULL)) {
10688                     sprintf(buffer, "%s/%s.%s.archive",
10689                             arcDir,
10690                             appData.cmailGameName,
10691                             gameInfo.date);
10692                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10693                     cmailMsgLoaded = FALSE;
10694                 }
10695             }
10696
10697             DisplayInformation(msg);
10698             pclose(commandOutput);
10699         }
10700     } else {
10701         if ((*cmailMsg) != '\0') {
10702             DisplayInformation(cmailMsg);
10703         }
10704     }
10705
10706     return;
10707 #endif /* !WIN32 */
10708 }
10709
10710 char *
10711 CmailMsg()
10712 {
10713 #if WIN32
10714     return NULL;
10715 #else
10716     int  prependComma = 0;
10717     char number[5];
10718     char string[MSG_SIZ];       /* Space for game-list */
10719     int  i;
10720
10721     if (!cmailMsgLoaded) return "";
10722
10723     if (cmailMailedMove) {
10724         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10725     } else {
10726         /* Create a list of games left */
10727         sprintf(string, "[");
10728         for (i = 0; i < nCmailGames; i ++) {
10729             if (! (   cmailMoveRegistered[i]
10730                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10731                 if (prependComma) {
10732                     sprintf(number, ",%d", i + 1);
10733                 } else {
10734                     sprintf(number, "%d", i + 1);
10735                     prependComma = 1;
10736                 }
10737
10738                 strcat(string, number);
10739             }
10740         }
10741         strcat(string, "]");
10742
10743         if (nCmailMovesRegistered + nCmailResults == 0) {
10744             switch (nCmailGames) {
10745               case 1:
10746                 sprintf(cmailMsg,
10747                         _("Still need to make move for game\n"));
10748                 break;
10749
10750               case 2:
10751                 sprintf(cmailMsg,
10752                         _("Still need to make moves for both games\n"));
10753                 break;
10754
10755               default:
10756                 sprintf(cmailMsg,
10757                         _("Still need to make moves for all %d games\n"),
10758                         nCmailGames);
10759                 break;
10760             }
10761         } else {
10762             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10763               case 1:
10764                 sprintf(cmailMsg,
10765                         _("Still need to make a move for game %s\n"),
10766                         string);
10767                 break;
10768
10769               case 0:
10770                 if (nCmailResults == nCmailGames) {
10771                     sprintf(cmailMsg, _("No unfinished games\n"));
10772                 } else {
10773                     sprintf(cmailMsg, _("Ready to send mail\n"));
10774                 }
10775                 break;
10776
10777               default:
10778                 sprintf(cmailMsg,
10779                         _("Still need to make moves for games %s\n"),
10780                         string);
10781             }
10782         }
10783     }
10784     return cmailMsg;
10785 #endif /* WIN32 */
10786 }
10787
10788 void
10789 ResetGameEvent()
10790 {
10791     if (gameMode == Training)
10792       SetTrainingModeOff();
10793
10794     Reset(TRUE, TRUE);
10795     cmailMsgLoaded = FALSE;
10796     if (appData.icsActive) {
10797       SendToICS(ics_prefix);
10798       SendToICS("refresh\n");
10799     }
10800 }
10801
10802 void
10803 ExitEvent(status)
10804      int status;
10805 {
10806     exiting++;
10807     if (exiting > 2) {
10808       /* Give up on clean exit */
10809       exit(status);
10810     }
10811     if (exiting > 1) {
10812       /* Keep trying for clean exit */
10813       return;
10814     }
10815
10816     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10817
10818     if (telnetISR != NULL) {
10819       RemoveInputSource(telnetISR);
10820     }
10821     if (icsPR != NoProc) {
10822       DestroyChildProcess(icsPR, TRUE);
10823     }
10824
10825     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10826     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10827
10828     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10829     /* make sure this other one finishes before killing it!                  */
10830     if(endingGame) { int count = 0;
10831         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10832         while(endingGame && count++ < 10) DoSleep(1);
10833         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10834     }
10835
10836     /* Kill off chess programs */
10837     if (first.pr != NoProc) {
10838         ExitAnalyzeMode();
10839
10840         DoSleep( appData.delayBeforeQuit );
10841         SendToProgram("quit\n", &first);
10842         DoSleep( appData.delayAfterQuit );
10843         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10844     }
10845     if (second.pr != NoProc) {
10846         DoSleep( appData.delayBeforeQuit );
10847         SendToProgram("quit\n", &second);
10848         DoSleep( appData.delayAfterQuit );
10849         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10850     }
10851     if (first.isr != NULL) {
10852         RemoveInputSource(first.isr);
10853     }
10854     if (second.isr != NULL) {
10855         RemoveInputSource(second.isr);
10856     }
10857
10858     ShutDownFrontEnd();
10859     exit(status);
10860 }
10861
10862 void
10863 PauseEvent()
10864 {
10865     if (appData.debugMode)
10866         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10867     if (pausing) {
10868         pausing = FALSE;
10869         ModeHighlight();
10870         if (gameMode == MachinePlaysWhite ||
10871             gameMode == MachinePlaysBlack) {
10872             StartClocks();
10873         } else {
10874             DisplayBothClocks();
10875         }
10876         if (gameMode == PlayFromGameFile) {
10877             if (appData.timeDelay >= 0)
10878                 AutoPlayGameLoop();
10879         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10880             Reset(FALSE, TRUE);
10881             SendToICS(ics_prefix);
10882             SendToICS("refresh\n");
10883         } else if (currentMove < forwardMostMove) {
10884             ForwardInner(forwardMostMove);
10885         }
10886         pauseExamInvalid = FALSE;
10887     } else {
10888         switch (gameMode) {
10889           default:
10890             return;
10891           case IcsExamining:
10892             pauseExamForwardMostMove = forwardMostMove;
10893             pauseExamInvalid = FALSE;
10894             /* fall through */
10895           case IcsObserving:
10896           case IcsPlayingWhite:
10897           case IcsPlayingBlack:
10898             pausing = TRUE;
10899             ModeHighlight();
10900             return;
10901           case PlayFromGameFile:
10902             (void) StopLoadGameTimer();
10903             pausing = TRUE;
10904             ModeHighlight();
10905             break;
10906           case BeginningOfGame:
10907             if (appData.icsActive) return;
10908             /* else fall through */
10909           case MachinePlaysWhite:
10910           case MachinePlaysBlack:
10911           case TwoMachinesPlay:
10912             if (forwardMostMove == 0)
10913               return;           /* don't pause if no one has moved */
10914             if ((gameMode == MachinePlaysWhite &&
10915                  !WhiteOnMove(forwardMostMove)) ||
10916                 (gameMode == MachinePlaysBlack &&
10917                  WhiteOnMove(forwardMostMove))) {
10918                 StopClocks();
10919             }
10920             pausing = TRUE;
10921             ModeHighlight();
10922             break;
10923         }
10924     }
10925 }
10926
10927 void
10928 EditCommentEvent()
10929 {
10930     char title[MSG_SIZ];
10931
10932     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10933         strcpy(title, _("Edit comment"));
10934     } else {
10935         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10936                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10937                 parseList[currentMove - 1]);
10938     }
10939
10940     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10941 }
10942
10943
10944 void
10945 EditTagsEvent()
10946 {
10947     char *tags = PGNTags(&gameInfo);
10948     EditTagsPopUp(tags);
10949     free(tags);
10950 }
10951
10952 void
10953 AnalyzeModeEvent()
10954 {
10955     if (appData.noChessProgram || gameMode == AnalyzeMode)
10956       return;
10957
10958     if (gameMode != AnalyzeFile) {
10959         if (!appData.icsEngineAnalyze) {
10960                EditGameEvent();
10961                if (gameMode != EditGame) return;
10962         }
10963         ResurrectChessProgram();
10964         SendToProgram("analyze\n", &first);
10965         first.analyzing = TRUE;
10966         /*first.maybeThinking = TRUE;*/
10967         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10968         EngineOutputPopUp();
10969     }
10970     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10971     pausing = FALSE;
10972     ModeHighlight();
10973     SetGameInfo();
10974
10975     StartAnalysisClock();
10976     GetTimeMark(&lastNodeCountTime);
10977     lastNodeCount = 0;
10978 }
10979
10980 void
10981 AnalyzeFileEvent()
10982 {
10983     if (appData.noChessProgram || gameMode == AnalyzeFile)
10984       return;
10985
10986     if (gameMode != AnalyzeMode) {
10987         EditGameEvent();
10988         if (gameMode != EditGame) return;
10989         ResurrectChessProgram();
10990         SendToProgram("analyze\n", &first);
10991         first.analyzing = TRUE;
10992         /*first.maybeThinking = TRUE;*/
10993         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10994         EngineOutputPopUp();
10995     }
10996     gameMode = AnalyzeFile;
10997     pausing = FALSE;
10998     ModeHighlight();
10999     SetGameInfo();
11000
11001     StartAnalysisClock();
11002     GetTimeMark(&lastNodeCountTime);
11003     lastNodeCount = 0;
11004 }
11005
11006 void
11007 MachineWhiteEvent()
11008 {
11009     char buf[MSG_SIZ];
11010     char *bookHit = NULL;
11011
11012     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11013       return;
11014
11015
11016     if (gameMode == PlayFromGameFile ||
11017         gameMode == TwoMachinesPlay  ||
11018         gameMode == Training         ||
11019         gameMode == AnalyzeMode      ||
11020         gameMode == EndOfGame)
11021         EditGameEvent();
11022
11023     if (gameMode == EditPosition) 
11024         EditPositionDone(TRUE);
11025
11026     if (!WhiteOnMove(currentMove)) {
11027         DisplayError(_("It is not White's turn"), 0);
11028         return;
11029     }
11030
11031     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11032       ExitAnalyzeMode();
11033
11034     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11035         gameMode == AnalyzeFile)
11036         TruncateGame();
11037
11038     ResurrectChessProgram();    /* in case it isn't running */
11039     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11040         gameMode = MachinePlaysWhite;
11041         ResetClocks();
11042     } else
11043     gameMode = MachinePlaysWhite;
11044     pausing = FALSE;
11045     ModeHighlight();
11046     SetGameInfo();
11047     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11048     DisplayTitle(buf);
11049     if (first.sendName) {
11050       sprintf(buf, "name %s\n", gameInfo.black);
11051       SendToProgram(buf, &first);
11052     }
11053     if (first.sendTime) {
11054       if (first.useColors) {
11055         SendToProgram("black\n", &first); /*gnu kludge*/
11056       }
11057       SendTimeRemaining(&first, TRUE);
11058     }
11059     if (first.useColors) {
11060       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11061     }
11062     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11063     SetMachineThinkingEnables();
11064     first.maybeThinking = TRUE;
11065     StartClocks();
11066     firstMove = FALSE;
11067
11068     if (appData.autoFlipView && !flipView) {
11069       flipView = !flipView;
11070       DrawPosition(FALSE, NULL);
11071       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11072     }
11073
11074     if(bookHit) { // [HGM] book: simulate book reply
11075         static char bookMove[MSG_SIZ]; // a bit generous?
11076
11077         programStats.nodes = programStats.depth = programStats.time =
11078         programStats.score = programStats.got_only_move = 0;
11079         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11080
11081         strcpy(bookMove, "move ");
11082         strcat(bookMove, bookHit);
11083         HandleMachineMove(bookMove, &first);
11084     }
11085 }
11086
11087 void
11088 MachineBlackEvent()
11089 {
11090   char buf[MSG_SIZ];
11091   char *bookHit = NULL;
11092   
11093   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11094     return;
11095   
11096   
11097   if (gameMode == PlayFromGameFile 
11098       || gameMode == TwoMachinesPlay  
11099       || gameMode == Training     
11100       || gameMode == AnalyzeMode
11101       || gameMode == EndOfGame)
11102     EditGameEvent();
11103   
11104   if (gameMode == EditPosition) 
11105     EditPositionDone(TRUE);
11106   
11107   if (WhiteOnMove(currentMove)) 
11108     {
11109       DisplayError(_("It is not Black's turn"), 0);
11110       return;
11111     }
11112   
11113   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11114     ExitAnalyzeMode();
11115   
11116   if (gameMode == EditGame || gameMode == AnalyzeMode 
11117       || gameMode == AnalyzeFile)
11118     TruncateGame();
11119   
11120   ResurrectChessProgram();      /* in case it isn't running */
11121   gameMode = MachinePlaysBlack;
11122   pausing  = FALSE;
11123   ModeHighlight();
11124   SetGameInfo();
11125   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11126   DisplayTitle(buf);
11127   if (first.sendName) 
11128     {
11129       sprintf(buf, "name %s\n", gameInfo.white);
11130       SendToProgram(buf, &first);
11131     }
11132   if (first.sendTime) 
11133     {
11134       if (first.useColors) 
11135         {
11136           SendToProgram("white\n", &first); /*gnu kludge*/
11137         }
11138       SendTimeRemaining(&first, FALSE);
11139     }
11140   if (first.useColors) 
11141     {
11142       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11143     }
11144   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11145   SetMachineThinkingEnables();
11146   first.maybeThinking = TRUE;
11147   StartClocks();
11148   
11149   if (appData.autoFlipView && flipView) 
11150     {
11151       flipView = !flipView;
11152       DrawPosition(FALSE, NULL);
11153       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11154     }
11155   if(bookHit) 
11156     { // [HGM] book: simulate book reply
11157       static char bookMove[MSG_SIZ]; // a bit generous?
11158       
11159       programStats.nodes = programStats.depth = programStats.time 
11160         = programStats.score = programStats.got_only_move = 0;
11161       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11162       
11163       strcpy(bookMove, "move ");
11164       strcat(bookMove, bookHit);
11165       HandleMachineMove(bookMove, &first);
11166     }
11167   return;
11168 }
11169
11170
11171 void
11172 DisplayTwoMachinesTitle()
11173 {
11174     char buf[MSG_SIZ];
11175     if (appData.matchGames > 0) {
11176         if (first.twoMachinesColor[0] == 'w') {
11177             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11178                     gameInfo.white, gameInfo.black,
11179                     first.matchWins, second.matchWins,
11180                     matchGame - 1 - (first.matchWins + second.matchWins));
11181         } else {
11182             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11183                     gameInfo.white, gameInfo.black,
11184                     second.matchWins, first.matchWins,
11185                     matchGame - 1 - (first.matchWins + second.matchWins));
11186         }
11187     } else {
11188         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11189     }
11190     DisplayTitle(buf);
11191 }
11192
11193 void
11194 TwoMachinesEvent P((void))
11195 {
11196     int i;
11197     char buf[MSG_SIZ];
11198     ChessProgramState *onmove;
11199     char *bookHit = NULL;
11200
11201     if (appData.noChessProgram) return;
11202
11203     switch (gameMode) {
11204       case TwoMachinesPlay:
11205         return;
11206       case MachinePlaysWhite:
11207       case MachinePlaysBlack:
11208         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11209             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11210             return;
11211         }
11212         /* fall through */
11213       case BeginningOfGame:
11214       case PlayFromGameFile:
11215       case EndOfGame:
11216         EditGameEvent();
11217         if (gameMode != EditGame) return;
11218         break;
11219       case EditPosition:
11220         EditPositionDone(TRUE);
11221         break;
11222       case AnalyzeMode:
11223       case AnalyzeFile:
11224         ExitAnalyzeMode();
11225         break;
11226       case EditGame:
11227       default:
11228         break;
11229     }
11230
11231 //    forwardMostMove = currentMove;
11232     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11233     ResurrectChessProgram();    /* in case first program isn't running */
11234
11235     if (second.pr == NULL) {
11236         StartChessProgram(&second);
11237         if (second.protocolVersion == 1) {
11238           TwoMachinesEventIfReady();
11239         } else {
11240           /* kludge: allow timeout for initial "feature" command */
11241           FreezeUI();
11242           DisplayMessage("", _("Starting second chess program"));
11243           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11244         }
11245         return;
11246     }
11247     DisplayMessage("", "");
11248     InitChessProgram(&second, FALSE);
11249     SendToProgram("force\n", &second);
11250     if (startedFromSetupPosition) {
11251         SendBoard(&second, backwardMostMove);
11252     if (appData.debugMode) {
11253         fprintf(debugFP, "Two Machines\n");
11254     }
11255     }
11256     for (i = backwardMostMove; i < forwardMostMove; i++) {
11257         SendMoveToProgram(i, &second);
11258     }
11259
11260     gameMode = TwoMachinesPlay;
11261     pausing = FALSE;
11262     ModeHighlight();
11263     SetGameInfo();
11264     DisplayTwoMachinesTitle();
11265     firstMove = TRUE;
11266     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11267         onmove = &first;
11268     } else {
11269         onmove = &second;
11270     }
11271
11272     SendToProgram(first.computerString, &first);
11273     if (first.sendName) {
11274       sprintf(buf, "name %s\n", second.tidy);
11275       SendToProgram(buf, &first);
11276     }
11277     SendToProgram(second.computerString, &second);
11278     if (second.sendName) {
11279       sprintf(buf, "name %s\n", first.tidy);
11280       SendToProgram(buf, &second);
11281     }
11282
11283     ResetClocks();
11284     if (!first.sendTime || !second.sendTime) {
11285         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11286         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11287     }
11288     if (onmove->sendTime) {
11289       if (onmove->useColors) {
11290         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11291       }
11292       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11293     }
11294     if (onmove->useColors) {
11295       SendToProgram(onmove->twoMachinesColor, onmove);
11296     }
11297     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11298 //    SendToProgram("go\n", onmove);
11299     onmove->maybeThinking = TRUE;
11300     SetMachineThinkingEnables();
11301
11302     StartClocks();
11303
11304     if(bookHit) { // [HGM] book: simulate book reply
11305         static char bookMove[MSG_SIZ]; // a bit generous?
11306
11307         programStats.nodes = programStats.depth = programStats.time =
11308         programStats.score = programStats.got_only_move = 0;
11309         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11310
11311         strcpy(bookMove, "move ");
11312         strcat(bookMove, bookHit);
11313         savedMessage = bookMove; // args for deferred call
11314         savedState = onmove;
11315         ScheduleDelayedEvent(DeferredBookMove, 1);
11316     }
11317 }
11318
11319 void
11320 TrainingEvent()
11321 {
11322     if (gameMode == Training) {
11323       SetTrainingModeOff();
11324       gameMode = PlayFromGameFile;
11325       DisplayMessage("", _("Training mode off"));
11326     } else {
11327       gameMode = Training;
11328       animateTraining = appData.animate;
11329
11330       /* make sure we are not already at the end of the game */
11331       if (currentMove < forwardMostMove) {
11332         SetTrainingModeOn();
11333         DisplayMessage("", _("Training mode on"));
11334       } else {
11335         gameMode = PlayFromGameFile;
11336         DisplayError(_("Already at end of game"), 0);
11337       }
11338     }
11339     ModeHighlight();
11340 }
11341
11342 void
11343 IcsClientEvent()
11344 {
11345     if (!appData.icsActive) return;
11346     switch (gameMode) {
11347       case IcsPlayingWhite:
11348       case IcsPlayingBlack:
11349       case IcsObserving:
11350       case IcsIdle:
11351       case BeginningOfGame:
11352       case IcsExamining:
11353         return;
11354
11355       case EditGame:
11356         break;
11357
11358       case EditPosition:
11359         EditPositionDone(TRUE);
11360         break;
11361
11362       case AnalyzeMode:
11363       case AnalyzeFile:
11364         ExitAnalyzeMode();
11365         break;
11366
11367       default:
11368         EditGameEvent();
11369         break;
11370     }
11371
11372     gameMode = IcsIdle;
11373     ModeHighlight();
11374     return;
11375 }
11376
11377
11378 void
11379 EditGameEvent()
11380 {
11381     int i;
11382
11383     switch (gameMode) {
11384       case Training:
11385         SetTrainingModeOff();
11386         break;
11387       case MachinePlaysWhite:
11388       case MachinePlaysBlack:
11389       case BeginningOfGame:
11390         SendToProgram("force\n", &first);
11391         SetUserThinkingEnables();
11392         break;
11393       case PlayFromGameFile:
11394         (void) StopLoadGameTimer();
11395         if (gameFileFP != NULL) {
11396             gameFileFP = NULL;
11397         }
11398         break;
11399       case EditPosition:
11400         EditPositionDone(TRUE);
11401         break;
11402       case AnalyzeMode:
11403       case AnalyzeFile:
11404         ExitAnalyzeMode();
11405         SendToProgram("force\n", &first);
11406         break;
11407       case TwoMachinesPlay:
11408         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11409         ResurrectChessProgram();
11410         SetUserThinkingEnables();
11411         break;
11412       case EndOfGame:
11413         ResurrectChessProgram();
11414         break;
11415       case IcsPlayingBlack:
11416       case IcsPlayingWhite:
11417         DisplayError(_("Warning: You are still playing a game"), 0);
11418         break;
11419       case IcsObserving:
11420         DisplayError(_("Warning: You are still observing a game"), 0);
11421         break;
11422       case IcsExamining:
11423         DisplayError(_("Warning: You are still examining a game"), 0);
11424         break;
11425       case IcsIdle:
11426         break;
11427       case EditGame:
11428       default:
11429         return;
11430     }
11431
11432     pausing = FALSE;
11433     StopClocks();
11434     first.offeredDraw = second.offeredDraw = 0;
11435
11436     if (gameMode == PlayFromGameFile) {
11437         whiteTimeRemaining = timeRemaining[0][currentMove];
11438         blackTimeRemaining = timeRemaining[1][currentMove];
11439         DisplayTitle("");
11440     }
11441
11442     if (gameMode == MachinePlaysWhite ||
11443         gameMode == MachinePlaysBlack ||
11444         gameMode == TwoMachinesPlay ||
11445         gameMode == EndOfGame) {
11446         i = forwardMostMove;
11447         while (i > currentMove) {
11448             SendToProgram("undo\n", &first);
11449             i--;
11450         }
11451         whiteTimeRemaining = timeRemaining[0][currentMove];
11452         blackTimeRemaining = timeRemaining[1][currentMove];
11453         DisplayBothClocks();
11454         if (whiteFlag || blackFlag) {
11455             whiteFlag = blackFlag = 0;
11456         }
11457         DisplayTitle("");
11458     }
11459
11460     gameMode = EditGame;
11461     ModeHighlight();
11462     SetGameInfo();
11463 }
11464
11465
11466 void
11467 EditPositionEvent()
11468 {
11469     if (gameMode == EditPosition) {
11470         EditGameEvent();
11471         return;
11472     }
11473
11474     EditGameEvent();
11475     if (gameMode != EditGame) return;
11476
11477     gameMode = EditPosition;
11478     ModeHighlight();
11479     SetGameInfo();
11480     if (currentMove > 0)
11481       CopyBoard(boards[0], boards[currentMove]);
11482
11483     blackPlaysFirst = !WhiteOnMove(currentMove);
11484     ResetClocks();
11485     currentMove = forwardMostMove = backwardMostMove = 0;
11486     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11487     DisplayMove(-1);
11488 }
11489
11490 void
11491 ExitAnalyzeMode()
11492 {
11493     /* [DM] icsEngineAnalyze - possible call from other functions */
11494     if (appData.icsEngineAnalyze) {
11495         appData.icsEngineAnalyze = FALSE;
11496
11497         DisplayMessage("",_("Close ICS engine analyze..."));
11498     }
11499     if (first.analysisSupport && first.analyzing) {
11500       SendToProgram("exit\n", &first);
11501       first.analyzing = FALSE;
11502     }
11503     thinkOutput[0] = NULLCHAR;
11504 }
11505
11506 void
11507 EditPositionDone(Boolean fakeRights)
11508 {
11509     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11510
11511     startedFromSetupPosition = TRUE;
11512     InitChessProgram(&first, FALSE);
11513     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11514       boards[0][EP_STATUS] = EP_NONE;
11515       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11516     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11517         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11518         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11519       } else boards[0][CASTLING][2] = NoRights;
11520     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11521         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11522         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11523       } else boards[0][CASTLING][5] = NoRights;
11524     }
11525     SendToProgram("force\n", &first);
11526     if (blackPlaysFirst) {
11527         strcpy(moveList[0], "");
11528         strcpy(parseList[0], "");
11529         currentMove = forwardMostMove = backwardMostMove = 1;
11530         CopyBoard(boards[1], boards[0]);
11531     } else {
11532         currentMove = forwardMostMove = backwardMostMove = 0;
11533     }
11534     SendBoard(&first, forwardMostMove);
11535     if (appData.debugMode) {
11536         fprintf(debugFP, "EditPosDone\n");
11537     }
11538     DisplayTitle("");
11539     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11540     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11541     gameMode = EditGame;
11542     ModeHighlight();
11543     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11544     ClearHighlights(); /* [AS] */
11545 }
11546
11547 /* Pause for `ms' milliseconds */
11548 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11549 void
11550 TimeDelay(ms)
11551      long ms;
11552 {
11553     TimeMark m1, m2;
11554
11555     GetTimeMark(&m1);
11556     do {
11557         GetTimeMark(&m2);
11558     } while (SubtractTimeMarks(&m2, &m1) < ms);
11559 }
11560
11561 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11562 void
11563 SendMultiLineToICS(buf)
11564      char *buf;
11565 {
11566     char temp[MSG_SIZ+1], *p;
11567     int len;
11568
11569     len = strlen(buf);
11570     if (len > MSG_SIZ)
11571       len = MSG_SIZ;
11572
11573     strncpy(temp, buf, len);
11574     temp[len] = 0;
11575
11576     p = temp;
11577     while (*p) {
11578         if (*p == '\n' || *p == '\r')
11579           *p = ' ';
11580         ++p;
11581     }
11582
11583     strcat(temp, "\n");
11584     SendToICS(temp);
11585     SendToPlayer(temp, strlen(temp));
11586 }
11587
11588 void
11589 SetWhiteToPlayEvent()
11590 {
11591     if (gameMode == EditPosition) {
11592         blackPlaysFirst = FALSE;
11593         DisplayBothClocks();    /* works because currentMove is 0 */
11594     } else if (gameMode == IcsExamining) {
11595         SendToICS(ics_prefix);
11596         SendToICS("tomove white\n");
11597     }
11598 }
11599
11600 void
11601 SetBlackToPlayEvent()
11602 {
11603     if (gameMode == EditPosition) {
11604         blackPlaysFirst = TRUE;
11605         currentMove = 1;        /* kludge */
11606         DisplayBothClocks();
11607         currentMove = 0;
11608     } else if (gameMode == IcsExamining) {
11609         SendToICS(ics_prefix);
11610         SendToICS("tomove black\n");
11611     }
11612 }
11613
11614 void
11615 EditPositionMenuEvent(selection, x, y)
11616      ChessSquare selection;
11617      int x, y;
11618 {
11619     char buf[MSG_SIZ];
11620     ChessSquare piece = boards[0][y][x];
11621
11622     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11623
11624     switch (selection) {
11625       case ClearBoard:
11626         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11627             SendToICS(ics_prefix);
11628             SendToICS("bsetup clear\n");
11629         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11630             SendToICS(ics_prefix);
11631             SendToICS("clearboard\n");
11632         } else {
11633             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11634                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11635                 for (y = 0; y < BOARD_HEIGHT; y++) {
11636                     if (gameMode == IcsExamining) {
11637                         if (boards[currentMove][y][x] != EmptySquare) {
11638                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11639                                     AAA + x, ONE + y);
11640                             SendToICS(buf);
11641                         }
11642                     } else {
11643                         boards[0][y][x] = p;
11644                     }
11645                 }
11646             }
11647         }
11648         if (gameMode == EditPosition) {
11649             DrawPosition(FALSE, boards[0]);
11650         }
11651         break;
11652
11653       case WhitePlay:
11654         SetWhiteToPlayEvent();
11655         break;
11656
11657       case BlackPlay:
11658         SetBlackToPlayEvent();
11659         break;
11660
11661       case EmptySquare:
11662         if (gameMode == IcsExamining) {
11663             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11664             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11665             SendToICS(buf);
11666         } else {
11667             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11668                 if(x == BOARD_LEFT-2) {
11669                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11670                     boards[0][y][1] = 0;
11671                 } else
11672                 if(x == BOARD_RGHT+1) {
11673                     if(y >= gameInfo.holdingsSize) break;
11674                     boards[0][y][BOARD_WIDTH-2] = 0;
11675                 } else break;
11676             }
11677             boards[0][y][x] = EmptySquare;
11678             DrawPosition(FALSE, boards[0]);
11679         }
11680         break;
11681
11682       case PromotePiece:
11683         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11684            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11685             selection = (ChessSquare) (PROMOTED piece);
11686         } else if(piece == EmptySquare) selection = WhiteSilver;
11687         else selection = (ChessSquare)((int)piece - 1);
11688         goto defaultlabel;
11689
11690       case DemotePiece:
11691         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11692            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11693             selection = (ChessSquare) (DEMOTED piece);
11694         } else if(piece == EmptySquare) selection = BlackSilver;
11695         else selection = (ChessSquare)((int)piece + 1);
11696         goto defaultlabel;
11697
11698       case WhiteQueen:
11699       case BlackQueen:
11700         if(gameInfo.variant == VariantShatranj ||
11701            gameInfo.variant == VariantXiangqi  ||
11702            gameInfo.variant == VariantCourier  ||
11703            gameInfo.variant == VariantMakruk     )
11704             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11705         goto defaultlabel;
11706
11707       case WhiteKing:
11708       case BlackKing:
11709         if(gameInfo.variant == VariantXiangqi)
11710             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11711         if(gameInfo.variant == VariantKnightmate)
11712             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11713       default:
11714         defaultlabel:
11715         if (gameMode == IcsExamining) {
11716             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11717             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11718                     PieceToChar(selection), AAA + x, ONE + y);
11719             SendToICS(buf);
11720         } else {
11721             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11722                 int n;
11723                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11724                     n = PieceToNumber(selection - BlackPawn);
11725                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11726                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11727                     boards[0][BOARD_HEIGHT-1-n][1]++;
11728                 } else
11729                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11730                     n = PieceToNumber(selection);
11731                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11732                     boards[0][n][BOARD_WIDTH-1] = selection;
11733                     boards[0][n][BOARD_WIDTH-2]++;
11734                 }
11735             } else
11736             boards[0][y][x] = selection;
11737             DrawPosition(TRUE, boards[0]);
11738         }
11739         break;
11740     }
11741 }
11742
11743
11744 void
11745 DropMenuEvent(selection, x, y)
11746      ChessSquare selection;
11747      int x, y;
11748 {
11749     ChessMove moveType;
11750
11751     switch (gameMode) {
11752       case IcsPlayingWhite:
11753       case MachinePlaysBlack:
11754         if (!WhiteOnMove(currentMove)) {
11755             DisplayMoveError(_("It is Black's turn"));
11756             return;
11757         }
11758         moveType = WhiteDrop;
11759         break;
11760       case IcsPlayingBlack:
11761       case MachinePlaysWhite:
11762         if (WhiteOnMove(currentMove)) {
11763             DisplayMoveError(_("It is White's turn"));
11764             return;
11765         }
11766         moveType = BlackDrop;
11767         break;
11768       case EditGame:
11769         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11770         break;
11771       default:
11772         return;
11773     }
11774
11775     if (moveType == BlackDrop && selection < BlackPawn) {
11776       selection = (ChessSquare) ((int) selection
11777                                  + (int) BlackPawn - (int) WhitePawn);
11778     }
11779     if (boards[currentMove][y][x] != EmptySquare) {
11780         DisplayMoveError(_("That square is occupied"));
11781         return;
11782     }
11783
11784     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11785 }
11786
11787 void
11788 AcceptEvent()
11789 {
11790     /* Accept a pending offer of any kind from opponent */
11791
11792     if (appData.icsActive) {
11793         SendToICS(ics_prefix);
11794         SendToICS("accept\n");
11795     } else if (cmailMsgLoaded) {
11796         if (currentMove == cmailOldMove &&
11797             commentList[cmailOldMove] != NULL &&
11798             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11799                    "Black offers a draw" : "White offers a draw")) {
11800             TruncateGame();
11801             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11802             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11803         } else {
11804             DisplayError(_("There is no pending offer on this move"), 0);
11805             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11806         }
11807     } else {
11808         /* Not used for offers from chess program */
11809     }
11810 }
11811
11812 void
11813 DeclineEvent()
11814 {
11815     /* Decline a pending offer of any kind from opponent */
11816
11817     if (appData.icsActive) {
11818         SendToICS(ics_prefix);
11819         SendToICS("decline\n");
11820     } else if (cmailMsgLoaded) {
11821         if (currentMove == cmailOldMove &&
11822             commentList[cmailOldMove] != NULL &&
11823             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11824                    "Black offers a draw" : "White offers a draw")) {
11825 #ifdef NOTDEF
11826             AppendComment(cmailOldMove, "Draw declined", TRUE);
11827             DisplayComment(cmailOldMove - 1, "Draw declined");
11828 #endif /*NOTDEF*/
11829         } else {
11830             DisplayError(_("There is no pending offer on this move"), 0);
11831         }
11832     } else {
11833         /* Not used for offers from chess program */
11834     }
11835 }
11836
11837 void
11838 RematchEvent()
11839 {
11840     /* Issue ICS rematch command */
11841     if (appData.icsActive) {
11842         SendToICS(ics_prefix);
11843         SendToICS("rematch\n");
11844     }
11845 }
11846
11847 void
11848 CallFlagEvent()
11849 {
11850     /* Call your opponent's flag (claim a win on time) */
11851     if (appData.icsActive) {
11852         SendToICS(ics_prefix);
11853         SendToICS("flag\n");
11854     } else {
11855         switch (gameMode) {
11856           default:
11857             return;
11858           case MachinePlaysWhite:
11859             if (whiteFlag) {
11860                 if (blackFlag)
11861                   GameEnds(GameIsDrawn, "Both players ran out of time",
11862                            GE_PLAYER);
11863                 else
11864                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11865             } else {
11866                 DisplayError(_("Your opponent is not out of time"), 0);
11867             }
11868             break;
11869           case MachinePlaysBlack:
11870             if (blackFlag) {
11871                 if (whiteFlag)
11872                   GameEnds(GameIsDrawn, "Both players ran out of time",
11873                            GE_PLAYER);
11874                 else
11875                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11876             } else {
11877                 DisplayError(_("Your opponent is not out of time"), 0);
11878             }
11879             break;
11880         }
11881     }
11882 }
11883
11884 void
11885 DrawEvent()
11886 {
11887     /* Offer draw or accept pending draw offer from opponent */
11888
11889     if (appData.icsActive) {
11890         /* Note: tournament rules require draw offers to be
11891            made after you make your move but before you punch
11892            your clock.  Currently ICS doesn't let you do that;
11893            instead, you immediately punch your clock after making
11894            a move, but you can offer a draw at any time. */
11895
11896         SendToICS(ics_prefix);
11897         SendToICS("draw\n");
11898     } else if (cmailMsgLoaded) {
11899         if (currentMove == cmailOldMove &&
11900             commentList[cmailOldMove] != NULL &&
11901             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11902                    "Black offers a draw" : "White offers a draw")) {
11903             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11904             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11905         } else if (currentMove == cmailOldMove + 1) {
11906             char *offer = WhiteOnMove(cmailOldMove) ?
11907               "White offers a draw" : "Black offers a draw";
11908             AppendComment(currentMove, offer, TRUE);
11909             DisplayComment(currentMove - 1, offer);
11910             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11911         } else {
11912             DisplayError(_("You must make your move before offering a draw"), 0);
11913             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11914         }
11915     } else if (first.offeredDraw) {
11916         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11917     } else {
11918         if (first.sendDrawOffers) {
11919             SendToProgram("draw\n", &first);
11920             userOfferedDraw = TRUE;
11921         }
11922     }
11923 }
11924
11925 void
11926 AdjournEvent()
11927 {
11928     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11929
11930     if (appData.icsActive) {
11931         SendToICS(ics_prefix);
11932         SendToICS("adjourn\n");
11933     } else {
11934         /* Currently GNU Chess doesn't offer or accept Adjourns */
11935     }
11936 }
11937
11938
11939 void
11940 AbortEvent()
11941 {
11942     /* Offer Abort or accept pending Abort offer from opponent */
11943
11944     if (appData.icsActive) {
11945         SendToICS(ics_prefix);
11946         SendToICS("abort\n");
11947     } else {
11948         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11949     }
11950 }
11951
11952 void
11953 ResignEvent()
11954 {
11955     /* Resign.  You can do this even if it's not your turn. */
11956
11957     if (appData.icsActive) {
11958         SendToICS(ics_prefix);
11959         SendToICS("resign\n");
11960     } else {
11961         switch (gameMode) {
11962           case MachinePlaysWhite:
11963             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11964             break;
11965           case MachinePlaysBlack:
11966             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11967             break;
11968           case EditGame:
11969             if (cmailMsgLoaded) {
11970                 TruncateGame();
11971                 if (WhiteOnMove(cmailOldMove)) {
11972                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11973                 } else {
11974                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11975                 }
11976                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11977             }
11978             break;
11979           default:
11980             break;
11981         }
11982     }
11983 }
11984
11985
11986 void
11987 StopObservingEvent()
11988 {
11989     /* Stop observing current games */
11990     SendToICS(ics_prefix);
11991     SendToICS("unobserve\n");
11992 }
11993
11994 void
11995 StopExaminingEvent()
11996 {
11997     /* Stop observing current game */
11998     SendToICS(ics_prefix);
11999     SendToICS("unexamine\n");
12000 }
12001
12002 void
12003 ForwardInner(target)
12004      int target;
12005 {
12006     int limit;
12007
12008     if (appData.debugMode)
12009         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12010                 target, currentMove, forwardMostMove);
12011
12012     if (gameMode == EditPosition)
12013       return;
12014
12015     if (gameMode == PlayFromGameFile && !pausing)
12016       PauseEvent();
12017
12018     if (gameMode == IcsExamining && pausing)
12019       limit = pauseExamForwardMostMove;
12020     else
12021       limit = forwardMostMove;
12022
12023     if (target > limit) target = limit;
12024
12025     if (target > 0 && moveList[target - 1][0]) {
12026         int fromX, fromY, toX, toY;
12027         toX = moveList[target - 1][2] - AAA;
12028         toY = moveList[target - 1][3] - ONE;
12029         if (moveList[target - 1][1] == '@') {
12030             if (appData.highlightLastMove) {
12031                 SetHighlights(-1, -1, toX, toY);
12032             }
12033         } else {
12034             fromX = moveList[target - 1][0] - AAA;
12035             fromY = moveList[target - 1][1] - ONE;
12036             if (target == currentMove + 1) {
12037                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12038             }
12039             if (appData.highlightLastMove) {
12040                 SetHighlights(fromX, fromY, toX, toY);
12041             }
12042         }
12043     }
12044     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12045         gameMode == Training || gameMode == PlayFromGameFile ||
12046         gameMode == AnalyzeFile) {
12047         while (currentMove < target) {
12048             SendMoveToProgram(currentMove++, &first);
12049         }
12050     } else {
12051         currentMove = target;
12052     }
12053
12054     if (gameMode == EditGame || gameMode == EndOfGame) {
12055         whiteTimeRemaining = timeRemaining[0][currentMove];
12056         blackTimeRemaining = timeRemaining[1][currentMove];
12057     }
12058     DisplayBothClocks();
12059     DisplayMove(currentMove - 1);
12060     DrawPosition(FALSE, boards[currentMove]);
12061     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12062     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12063         DisplayComment(currentMove - 1, commentList[currentMove]);
12064     }
12065 }
12066
12067
12068 void
12069 ForwardEvent()
12070 {
12071     if (gameMode == IcsExamining && !pausing) {
12072         SendToICS(ics_prefix);
12073         SendToICS("forward\n");
12074     } else {
12075         ForwardInner(currentMove + 1);
12076     }
12077 }
12078
12079 void
12080 ToEndEvent()
12081 {
12082     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12083         /* to optimze, we temporarily turn off analysis mode while we feed
12084          * the remaining moves to the engine. Otherwise we get analysis output
12085          * after each move.
12086          */
12087         if (first.analysisSupport) {
12088           SendToProgram("exit\nforce\n", &first);
12089           first.analyzing = FALSE;
12090         }
12091     }
12092
12093     if (gameMode == IcsExamining && !pausing) {
12094         SendToICS(ics_prefix);
12095         SendToICS("forward 999999\n");
12096     } else {
12097         ForwardInner(forwardMostMove);
12098     }
12099
12100     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12101         /* we have fed all the moves, so reactivate analysis mode */
12102         SendToProgram("analyze\n", &first);
12103         first.analyzing = TRUE;
12104         /*first.maybeThinking = TRUE;*/
12105         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12106     }
12107 }
12108
12109 void
12110 BackwardInner(target)
12111      int target;
12112 {
12113     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12114
12115     if (appData.debugMode)
12116         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12117                 target, currentMove, forwardMostMove);
12118
12119     if (gameMode == EditPosition) return;
12120     if (currentMove <= backwardMostMove) {
12121         ClearHighlights();
12122         DrawPosition(full_redraw, boards[currentMove]);
12123         return;
12124     }
12125     if (gameMode == PlayFromGameFile && !pausing)
12126       PauseEvent();
12127
12128     if (moveList[target][0]) {
12129         int fromX, fromY, toX, toY;
12130         toX = moveList[target][2] - AAA;
12131         toY = moveList[target][3] - ONE;
12132         if (moveList[target][1] == '@') {
12133             if (appData.highlightLastMove) {
12134                 SetHighlights(-1, -1, toX, toY);
12135             }
12136         } else {
12137             fromX = moveList[target][0] - AAA;
12138             fromY = moveList[target][1] - ONE;
12139             if (target == currentMove - 1) {
12140                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12141             }
12142             if (appData.highlightLastMove) {
12143                 SetHighlights(fromX, fromY, toX, toY);
12144             }
12145         }
12146     }
12147     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12148         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12149         while (currentMove > target) {
12150             SendToProgram("undo\n", &first);
12151             currentMove--;
12152         }
12153     } else {
12154         currentMove = target;
12155     }
12156
12157     if (gameMode == EditGame || gameMode == EndOfGame) {
12158         whiteTimeRemaining = timeRemaining[0][currentMove];
12159         blackTimeRemaining = timeRemaining[1][currentMove];
12160     }
12161     DisplayBothClocks();
12162     DisplayMove(currentMove - 1);
12163     DrawPosition(full_redraw, boards[currentMove]);
12164     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12165     // [HGM] PV info: routine tests if comment empty
12166     DisplayComment(currentMove - 1, commentList[currentMove]);
12167 }
12168
12169 void
12170 BackwardEvent()
12171 {
12172     if (gameMode == IcsExamining && !pausing) {
12173         SendToICS(ics_prefix);
12174         SendToICS("backward\n");
12175     } else {
12176         BackwardInner(currentMove - 1);
12177     }
12178 }
12179
12180 void
12181 ToStartEvent()
12182 {
12183     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12184         /* to optimize, we temporarily turn off analysis mode while we undo
12185          * all the moves. Otherwise we get analysis output after each undo.
12186          */
12187         if (first.analysisSupport) {
12188           SendToProgram("exit\nforce\n", &first);
12189           first.analyzing = FALSE;
12190         }
12191     }
12192
12193     if (gameMode == IcsExamining && !pausing) {
12194         SendToICS(ics_prefix);
12195         SendToICS("backward 999999\n");
12196     } else {
12197         BackwardInner(backwardMostMove);
12198     }
12199
12200     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12201         /* we have fed all the moves, so reactivate analysis mode */
12202         SendToProgram("analyze\n", &first);
12203         first.analyzing = TRUE;
12204         /*first.maybeThinking = TRUE;*/
12205         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12206     }
12207 }
12208
12209 void
12210 ToNrEvent(int to)
12211 {
12212   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12213   if (to >= forwardMostMove) to = forwardMostMove;
12214   if (to <= backwardMostMove) to = backwardMostMove;
12215   if (to < currentMove) {
12216     BackwardInner(to);
12217   } else {
12218     ForwardInner(to);
12219   }
12220 }
12221
12222 void
12223 RevertEvent()
12224 {
12225     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12226         return;
12227     }
12228     if (gameMode != IcsExamining) {
12229         DisplayError(_("You are not examining a game"), 0);
12230         return;
12231     }
12232     if (pausing) {
12233         DisplayError(_("You can't revert while pausing"), 0);
12234         return;
12235     }
12236     SendToICS(ics_prefix);
12237     SendToICS("revert\n");
12238 }
12239
12240 void
12241 RetractMoveEvent()
12242 {
12243     switch (gameMode) {
12244       case MachinePlaysWhite:
12245       case MachinePlaysBlack:
12246         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12247             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12248             return;
12249         }
12250         if (forwardMostMove < 2) return;
12251         currentMove = forwardMostMove = forwardMostMove - 2;
12252         whiteTimeRemaining = timeRemaining[0][currentMove];
12253         blackTimeRemaining = timeRemaining[1][currentMove];
12254         DisplayBothClocks();
12255         DisplayMove(currentMove - 1);
12256         ClearHighlights();/*!! could figure this out*/
12257         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12258         SendToProgram("remove\n", &first);
12259         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12260         break;
12261
12262       case BeginningOfGame:
12263       default:
12264         break;
12265
12266       case IcsPlayingWhite:
12267       case IcsPlayingBlack:
12268         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12269             SendToICS(ics_prefix);
12270             SendToICS("takeback 2\n");
12271         } else {
12272             SendToICS(ics_prefix);
12273             SendToICS("takeback 1\n");
12274         }
12275         break;
12276     }
12277 }
12278
12279 void
12280 MoveNowEvent()
12281 {
12282     ChessProgramState *cps;
12283
12284     switch (gameMode) {
12285       case MachinePlaysWhite:
12286         if (!WhiteOnMove(forwardMostMove)) {
12287             DisplayError(_("It is your turn"), 0);
12288             return;
12289         }
12290         cps = &first;
12291         break;
12292       case MachinePlaysBlack:
12293         if (WhiteOnMove(forwardMostMove)) {
12294             DisplayError(_("It is your turn"), 0);
12295             return;
12296         }
12297         cps = &first;
12298         break;
12299       case TwoMachinesPlay:
12300         if (WhiteOnMove(forwardMostMove) ==
12301             (first.twoMachinesColor[0] == 'w')) {
12302             cps = &first;
12303         } else {
12304             cps = &second;
12305         }
12306         break;
12307       case BeginningOfGame:
12308       default:
12309         return;
12310     }
12311     SendToProgram("?\n", cps);
12312 }
12313
12314 void
12315 TruncateGameEvent()
12316 {
12317     EditGameEvent();
12318     if (gameMode != EditGame) return;
12319     TruncateGame();
12320 }
12321
12322 void
12323 TruncateGame()
12324 {
12325     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12326     if (forwardMostMove > currentMove) {
12327         if (gameInfo.resultDetails != NULL) {
12328             free(gameInfo.resultDetails);
12329             gameInfo.resultDetails = NULL;
12330             gameInfo.result = GameUnfinished;
12331         }
12332         forwardMostMove = currentMove;
12333         HistorySet(parseList, backwardMostMove, forwardMostMove,
12334                    currentMove-1);
12335     }
12336 }
12337
12338 void
12339 HintEvent()
12340 {
12341     if (appData.noChessProgram) return;
12342     switch (gameMode) {
12343       case MachinePlaysWhite:
12344         if (WhiteOnMove(forwardMostMove)) {
12345             DisplayError(_("Wait until your turn"), 0);
12346             return;
12347         }
12348         break;
12349       case BeginningOfGame:
12350       case MachinePlaysBlack:
12351         if (!WhiteOnMove(forwardMostMove)) {
12352             DisplayError(_("Wait until your turn"), 0);
12353             return;
12354         }
12355         break;
12356       default:
12357         DisplayError(_("No hint available"), 0);
12358         return;
12359     }
12360     SendToProgram("hint\n", &first);
12361     hintRequested = TRUE;
12362 }
12363
12364 void
12365 BookEvent()
12366 {
12367     if (appData.noChessProgram) return;
12368     switch (gameMode) {
12369       case MachinePlaysWhite:
12370         if (WhiteOnMove(forwardMostMove)) {
12371             DisplayError(_("Wait until your turn"), 0);
12372             return;
12373         }
12374         break;
12375       case BeginningOfGame:
12376       case MachinePlaysBlack:
12377         if (!WhiteOnMove(forwardMostMove)) {
12378             DisplayError(_("Wait until your turn"), 0);
12379             return;
12380         }
12381         break;
12382       case EditPosition:
12383         EditPositionDone(TRUE);
12384         break;
12385       case TwoMachinesPlay:
12386         return;
12387       default:
12388         break;
12389     }
12390     SendToProgram("bk\n", &first);
12391     bookOutput[0] = NULLCHAR;
12392     bookRequested = TRUE;
12393 }
12394
12395 void
12396 AboutGameEvent()
12397 {
12398     char *tags = PGNTags(&gameInfo);
12399     TagsPopUp(tags, CmailMsg());
12400     free(tags);
12401 }
12402
12403 /* end button procedures */
12404
12405 void
12406 PrintPosition(fp, move)
12407      FILE *fp;
12408      int move;
12409 {
12410     int i, j;
12411
12412     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12413         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12414             char c = PieceToChar(boards[move][i][j]);
12415             fputc(c == 'x' ? '.' : c, fp);
12416             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12417         }
12418     }
12419     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12420       fprintf(fp, "white to play\n");
12421     else
12422       fprintf(fp, "black to play\n");
12423 }
12424
12425 void
12426 PrintOpponents(fp)
12427      FILE *fp;
12428 {
12429     if (gameInfo.white != NULL) {
12430         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12431     } else {
12432         fprintf(fp, "\n");
12433     }
12434 }
12435
12436 /* Find last component of program's own name, using some heuristics */
12437 void
12438 TidyProgramName(prog, host, buf)
12439      char *prog, *host, buf[MSG_SIZ];
12440 {
12441     char *p, *q;
12442     int local = (strcmp(host, "localhost") == 0);
12443     while (!local && (p = strchr(prog, ';')) != NULL) {
12444         p++;
12445         while (*p == ' ') p++;
12446         prog = p;
12447     }
12448     if (*prog == '"' || *prog == '\'') {
12449         q = strchr(prog + 1, *prog);
12450     } else {
12451         q = strchr(prog, ' ');
12452     }
12453     if (q == NULL) q = prog + strlen(prog);
12454     p = q;
12455     while (p >= prog && *p != '/' && *p != '\\') p--;
12456     p++;
12457     if(p == prog && *p == '"') p++;
12458     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12459     memcpy(buf, p, q - p);
12460     buf[q - p] = NULLCHAR;
12461     if (!local) {
12462         strcat(buf, "@");
12463         strcat(buf, host);
12464     }
12465 }
12466
12467 char *
12468 TimeControlTagValue()
12469 {
12470     char buf[MSG_SIZ];
12471     if (!appData.clockMode) {
12472         strcpy(buf, "-");
12473     } else if (movesPerSession > 0) {
12474         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12475     } else if (timeIncrement == 0) {
12476         sprintf(buf, "%ld", timeControl/1000);
12477     } else {
12478         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12479     }
12480     return StrSave(buf);
12481 }
12482
12483 void
12484 SetGameInfo()
12485 {
12486     /* This routine is used only for certain modes */
12487     VariantClass v = gameInfo.variant;
12488     ChessMove r = GameUnfinished;
12489     char *p = NULL;
12490
12491     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12492         r = gameInfo.result; 
12493         p = gameInfo.resultDetails; 
12494         gameInfo.resultDetails = NULL;
12495     }
12496     ClearGameInfo(&gameInfo);
12497     gameInfo.variant = v;
12498
12499     switch (gameMode) {
12500       case MachinePlaysWhite:
12501         gameInfo.event = StrSave( appData.pgnEventHeader );
12502         gameInfo.site = StrSave(HostName());
12503         gameInfo.date = PGNDate();
12504         gameInfo.round = StrSave("-");
12505         gameInfo.white = StrSave(first.tidy);
12506         gameInfo.black = StrSave(UserName());
12507         gameInfo.timeControl = TimeControlTagValue();
12508         break;
12509
12510       case MachinePlaysBlack:
12511         gameInfo.event = StrSave( appData.pgnEventHeader );
12512         gameInfo.site = StrSave(HostName());
12513         gameInfo.date = PGNDate();
12514         gameInfo.round = StrSave("-");
12515         gameInfo.white = StrSave(UserName());
12516         gameInfo.black = StrSave(first.tidy);
12517         gameInfo.timeControl = TimeControlTagValue();
12518         break;
12519
12520       case TwoMachinesPlay:
12521         gameInfo.event = StrSave( appData.pgnEventHeader );
12522         gameInfo.site = StrSave(HostName());
12523         gameInfo.date = PGNDate();
12524         if (matchGame > 0) {
12525             char buf[MSG_SIZ];
12526             sprintf(buf, "%d", matchGame);
12527             gameInfo.round = StrSave(buf);
12528         } else {
12529             gameInfo.round = StrSave("-");
12530         }
12531         if (first.twoMachinesColor[0] == 'w') {
12532             gameInfo.white = StrSave(first.tidy);
12533             gameInfo.black = StrSave(second.tidy);
12534         } else {
12535             gameInfo.white = StrSave(second.tidy);
12536             gameInfo.black = StrSave(first.tidy);
12537         }
12538         gameInfo.timeControl = TimeControlTagValue();
12539         break;
12540
12541       case EditGame:
12542         gameInfo.event = StrSave("Edited game");
12543         gameInfo.site = StrSave(HostName());
12544         gameInfo.date = PGNDate();
12545         gameInfo.round = StrSave("-");
12546         gameInfo.white = StrSave("-");
12547         gameInfo.black = StrSave("-");
12548         gameInfo.result = r;
12549         gameInfo.resultDetails = p;
12550         break;
12551
12552       case EditPosition:
12553         gameInfo.event = StrSave("Edited position");
12554         gameInfo.site = StrSave(HostName());
12555         gameInfo.date = PGNDate();
12556         gameInfo.round = StrSave("-");
12557         gameInfo.white = StrSave("-");
12558         gameInfo.black = StrSave("-");
12559         break;
12560
12561       case IcsPlayingWhite:
12562       case IcsPlayingBlack:
12563       case IcsObserving:
12564       case IcsExamining:
12565         break;
12566
12567       case PlayFromGameFile:
12568         gameInfo.event = StrSave("Game from non-PGN file");
12569         gameInfo.site = StrSave(HostName());
12570         gameInfo.date = PGNDate();
12571         gameInfo.round = StrSave("-");
12572         gameInfo.white = StrSave("?");
12573         gameInfo.black = StrSave("?");
12574         break;
12575
12576       default:
12577         break;
12578     }
12579 }
12580
12581 void
12582 ReplaceComment(index, text)
12583      int index;
12584      char *text;
12585 {
12586     int len;
12587
12588     while (*text == '\n') text++;
12589     len = strlen(text);
12590     while (len > 0 && text[len - 1] == '\n') len--;
12591
12592     if (commentList[index] != NULL)
12593       free(commentList[index]);
12594
12595     if (len == 0) {
12596         commentList[index] = NULL;
12597         return;
12598     }
12599   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12600       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12601       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12602     commentList[index] = (char *) malloc(len + 2);
12603     strncpy(commentList[index], text, len);
12604     commentList[index][len] = '\n';
12605     commentList[index][len + 1] = NULLCHAR;
12606   } else { 
12607     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12608     char *p;
12609     commentList[index] = (char *) malloc(len + 6);
12610     strcpy(commentList[index], "{\n");
12611     strncpy(commentList[index]+2, text, len);
12612     commentList[index][len+2] = NULLCHAR;
12613     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12614     strcat(commentList[index], "\n}\n");
12615   }
12616 }
12617
12618 void
12619 CrushCRs(text)
12620      char *text;
12621 {
12622   char *p = text;
12623   char *q = text;
12624   char ch;
12625
12626   do {
12627     ch = *p++;
12628     if (ch == '\r') continue;
12629     *q++ = ch;
12630   } while (ch != '\0');
12631 }
12632
12633 void
12634 AppendComment(index, text, addBraces)
12635      int index;
12636      char *text;
12637      Boolean addBraces; // [HGM] braces: tells if we should add {}
12638 {
12639     int oldlen, len;
12640     char *old;
12641
12642 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12643     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12644
12645     CrushCRs(text);
12646     while (*text == '\n') text++;
12647     len = strlen(text);
12648     while (len > 0 && text[len - 1] == '\n') len--;
12649
12650     if (len == 0) return;
12651
12652     if (commentList[index] != NULL) {
12653         old = commentList[index];
12654         oldlen = strlen(old);
12655         while(commentList[index][oldlen-1] ==  '\n')
12656           commentList[index][--oldlen] = NULLCHAR;
12657         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12658         strcpy(commentList[index], old);
12659         free(old);
12660         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12661         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12662           if(addBraces) addBraces = FALSE; else { text++; len--; }
12663           while (*text == '\n') { text++; len--; }
12664           commentList[index][--oldlen] = NULLCHAR;
12665       }
12666         if(addBraces) strcat(commentList[index], "\n{\n");
12667         else          strcat(commentList[index], "\n");
12668         strcat(commentList[index], text);
12669         if(addBraces) strcat(commentList[index], "\n}\n");
12670         else          strcat(commentList[index], "\n");
12671     } else {
12672         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12673         if(addBraces)
12674              strcpy(commentList[index], "{\n");
12675         else commentList[index][0] = NULLCHAR;
12676         strcat(commentList[index], text);
12677         strcat(commentList[index], "\n");
12678         if(addBraces) strcat(commentList[index], "}\n");
12679     }
12680 }
12681
12682 static char * FindStr( char * text, char * sub_text )
12683 {
12684     char * result = strstr( text, sub_text );
12685
12686     if( result != NULL ) {
12687         result += strlen( sub_text );
12688     }
12689
12690     return result;
12691 }
12692
12693 /* [AS] Try to extract PV info from PGN comment */
12694 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12695 char *GetInfoFromComment( int index, char * text )
12696 {
12697     char * sep = text;
12698
12699     if( text != NULL && index > 0 ) {
12700         int score = 0;
12701         int depth = 0;
12702         int time = -1, sec = 0, deci;
12703         char * s_eval = FindStr( text, "[%eval " );
12704         char * s_emt = FindStr( text, "[%emt " );
12705
12706         if( s_eval != NULL || s_emt != NULL ) {
12707             /* New style */
12708             char delim;
12709
12710             if( s_eval != NULL ) {
12711                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12712                     return text;
12713                 }
12714
12715                 if( delim != ']' ) {
12716                     return text;
12717                 }
12718             }
12719
12720             if( s_emt != NULL ) {
12721             }
12722                 return text;
12723         }
12724         else {
12725             /* We expect something like: [+|-]nnn.nn/dd */
12726             int score_lo = 0;
12727
12728             if(*text != '{') return text; // [HGM] braces: must be normal comment
12729
12730             sep = strchr( text, '/' );
12731             if( sep == NULL || sep < (text+4) ) {
12732                 return text;
12733             }
12734
12735             time = -1; sec = -1; deci = -1;
12736             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12737                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12738                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12739                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12740                 return text;
12741             }
12742
12743             if( score_lo < 0 || score_lo >= 100 ) {
12744                 return text;
12745             }
12746
12747             if(sec >= 0) time = 600*time + 10*sec; else
12748             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12749
12750             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12751
12752             /* [HGM] PV time: now locate end of PV info */
12753             while( *++sep >= '0' && *sep <= '9'); // strip depth
12754             if(time >= 0)
12755             while( *++sep >= '0' && *sep <= '9'); // strip time
12756             if(sec >= 0)
12757             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12758             if(deci >= 0)
12759             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12760             while(*sep == ' ') sep++;
12761         }
12762
12763         if( depth <= 0 ) {
12764             return text;
12765         }
12766
12767         if( time < 0 ) {
12768             time = -1;
12769         }
12770
12771         pvInfoList[index-1].depth = depth;
12772         pvInfoList[index-1].score = score;
12773         pvInfoList[index-1].time  = 10*time; // centi-sec
12774         if(*sep == '}') *sep = 0; else *--sep = '{';
12775     }
12776     return sep;
12777 }
12778
12779 void
12780 SendToProgram(message, cps)
12781      char *message;
12782      ChessProgramState *cps;
12783 {
12784     int count, outCount, error;
12785     char buf[MSG_SIZ];
12786
12787     if (cps->pr == NULL) return;
12788     Attention(cps);
12789
12790     if (appData.debugMode) {
12791         TimeMark now;
12792         GetTimeMark(&now);
12793         fprintf(debugFP, "%ld >%-6s: %s",
12794                 SubtractTimeMarks(&now, &programStartTime),
12795                 cps->which, message);
12796     }
12797
12798     count = strlen(message);
12799     outCount = OutputToProcess(cps->pr, message, count, &error);
12800     if (outCount < count && !exiting
12801                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12802         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12803         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12804             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12805                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12806                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12807             } else {
12808                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12809             }
12810             gameInfo.resultDetails = StrSave(buf);
12811         }
12812         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12813     }
12814 }
12815
12816 void
12817 ReceiveFromProgram(isr, closure, message, count, error)
12818      InputSourceRef isr;
12819      VOIDSTAR closure;
12820      char *message;
12821      int count;
12822      int error;
12823 {
12824     char *end_str;
12825     char buf[MSG_SIZ];
12826     ChessProgramState *cps = (ChessProgramState *)closure;
12827
12828     if (isr != cps->isr) return; /* Killed intentionally */
12829     if (count <= 0) {
12830         if (count == 0) {
12831             sprintf(buf,
12832                     _("Error: %s chess program (%s) exited unexpectedly"),
12833                     cps->which, cps->program);
12834         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12835                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12836                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12837                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12838                 } else {
12839                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12840                 }
12841                 gameInfo.resultDetails = StrSave(buf);
12842             }
12843             RemoveInputSource(cps->isr);
12844             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12845         } else {
12846             sprintf(buf,
12847                     _("Error reading from %s chess program (%s)"),
12848                     cps->which, cps->program);
12849             RemoveInputSource(cps->isr);
12850
12851             /* [AS] Program is misbehaving badly... kill it */
12852             if( count == -2 ) {
12853                 DestroyChildProcess( cps->pr, 9 );
12854                 cps->pr = NoProc;
12855             }
12856
12857             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12858         }
12859         return;
12860     }
12861
12862     if ((end_str = strchr(message, '\r')) != NULL)
12863       *end_str = NULLCHAR;
12864     if ((end_str = strchr(message, '\n')) != NULL)
12865       *end_str = NULLCHAR;
12866
12867     if (appData.debugMode) {
12868         TimeMark now; int print = 1;
12869         char *quote = ""; char c; int i;
12870
12871         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12872                 char start = message[0];
12873                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12874                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12875                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12876                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12877                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12878                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12879                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12880                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12881                         { quote = "# "; print = (appData.engineComments == 2); }
12882                 message[0] = start; // restore original message
12883         }
12884         if(print) {
12885                 GetTimeMark(&now);
12886                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12887                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12888                         quote,
12889                         message);
12890         }
12891     }
12892
12893     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12894     if (appData.icsEngineAnalyze) {
12895         if (strstr(message, "whisper") != NULL ||
12896              strstr(message, "kibitz") != NULL ||
12897             strstr(message, "tellics") != NULL) return;
12898     }
12899
12900     HandleMachineMove(message, cps);
12901 }
12902
12903
12904 void
12905 SendTimeControl(cps, mps, tc, inc, sd, st)
12906      ChessProgramState *cps;
12907      int mps, inc, sd, st;
12908      long tc;
12909 {
12910     char buf[MSG_SIZ];
12911     int seconds;
12912
12913     if( timeControl_2 > 0 ) {
12914         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12915             tc = timeControl_2;
12916         }
12917     }
12918     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12919     inc /= cps->timeOdds;
12920     st  /= cps->timeOdds;
12921
12922     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12923
12924     if (st > 0) {
12925       /* Set exact time per move, normally using st command */
12926       if (cps->stKludge) {
12927         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12928         seconds = st % 60;
12929         if (seconds == 0) {
12930           sprintf(buf, "level 1 %d\n", st/60);
12931         } else {
12932           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12933         }
12934       } else {
12935         sprintf(buf, "st %d\n", st);
12936       }
12937     } else {
12938       /* Set conventional or incremental time control, using level command */
12939       if (seconds == 0) {
12940         /* Note old gnuchess bug -- minutes:seconds used to not work.
12941            Fixed in later versions, but still avoid :seconds
12942            when seconds is 0. */
12943         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12944       } else {
12945         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12946                 seconds, inc/1000);
12947       }
12948     }
12949     SendToProgram(buf, cps);
12950
12951     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12952     /* Orthogonally, limit search to given depth */
12953     if (sd > 0) {
12954       if (cps->sdKludge) {
12955         sprintf(buf, "depth\n%d\n", sd);
12956       } else {
12957         sprintf(buf, "sd %d\n", sd);
12958       }
12959       SendToProgram(buf, cps);
12960     }
12961
12962     if(cps->nps > 0) { /* [HGM] nps */
12963         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12964         else {
12965                 sprintf(buf, "nps %d\n", cps->nps);
12966               SendToProgram(buf, cps);
12967         }
12968     }
12969 }
12970
12971 ChessProgramState *WhitePlayer()
12972 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12973 {
12974     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12975        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12976         return &second;
12977     return &first;
12978 }
12979
12980 void
12981 SendTimeRemaining(cps, machineWhite)
12982      ChessProgramState *cps;
12983      int /*boolean*/ machineWhite;
12984 {
12985     char message[MSG_SIZ];
12986     long time, otime;
12987
12988     /* Note: this routine must be called when the clocks are stopped
12989        or when they have *just* been set or switched; otherwise
12990        it will be off by the time since the current tick started.
12991     */
12992     if (machineWhite) {
12993         time = whiteTimeRemaining / 10;
12994         otime = blackTimeRemaining / 10;
12995     } else {
12996         time = blackTimeRemaining / 10;
12997         otime = whiteTimeRemaining / 10;
12998     }
12999     /* [HGM] translate opponent's time by time-odds factor */
13000     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13001     if (appData.debugMode) {
13002         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13003     }
13004
13005     if (time <= 0) time = 1;
13006     if (otime <= 0) otime = 1;
13007
13008     sprintf(message, "time %ld\n", time);
13009     SendToProgram(message, cps);
13010
13011     sprintf(message, "otim %ld\n", otime);
13012     SendToProgram(message, cps);
13013 }
13014
13015 int
13016 BoolFeature(p, name, loc, cps)
13017      char **p;
13018      char *name;
13019      int *loc;
13020      ChessProgramState *cps;
13021 {
13022   char buf[MSG_SIZ];
13023   int len = strlen(name);
13024   int val;
13025   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13026     (*p) += len + 1;
13027     sscanf(*p, "%d", &val);
13028     *loc = (val != 0);
13029     while (**p && **p != ' ') (*p)++;
13030     sprintf(buf, "accepted %s\n", name);
13031     SendToProgram(buf, cps);
13032     return TRUE;
13033   }
13034   return FALSE;
13035 }
13036
13037 int
13038 IntFeature(p, name, loc, cps)
13039      char **p;
13040      char *name;
13041      int *loc;
13042      ChessProgramState *cps;
13043 {
13044   char buf[MSG_SIZ];
13045   int len = strlen(name);
13046   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13047     (*p) += len + 1;
13048     sscanf(*p, "%d", loc);
13049     while (**p && **p != ' ') (*p)++;
13050     sprintf(buf, "accepted %s\n", name);
13051     SendToProgram(buf, cps);
13052     return TRUE;
13053   }
13054   return FALSE;
13055 }
13056
13057 int
13058 StringFeature(p, name, loc, cps)
13059      char **p;
13060      char *name;
13061      char loc[];
13062      ChessProgramState *cps;
13063 {
13064   char buf[MSG_SIZ];
13065   int len = strlen(name);
13066   if (strncmp((*p), name, len) == 0
13067       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13068     (*p) += len + 2;
13069     sscanf(*p, "%[^\"]", loc);
13070     while (**p && **p != '\"') (*p)++;
13071     if (**p == '\"') (*p)++;
13072     sprintf(buf, "accepted %s\n", name);
13073     SendToProgram(buf, cps);
13074     return TRUE;
13075   }
13076   return FALSE;
13077 }
13078
13079 int
13080 ParseOption(Option *opt, ChessProgramState *cps)
13081 // [HGM] options: process the string that defines an engine option, and determine
13082 // name, type, default value, and allowed value range
13083 {
13084         char *p, *q, buf[MSG_SIZ];
13085         int n, min = (-1)<<31, max = 1<<31, def;
13086
13087         if(p = strstr(opt->name, " -spin ")) {
13088             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13089             if(max < min) max = min; // enforce consistency
13090             if(def < min) def = min;
13091             if(def > max) def = max;
13092             opt->value = def;
13093             opt->min = min;
13094             opt->max = max;
13095             opt->type = Spin;
13096         } else if((p = strstr(opt->name, " -slider "))) {
13097             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13098             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13099             if(max < min) max = min; // enforce consistency
13100             if(def < min) def = min;
13101             if(def > max) def = max;
13102             opt->value = def;
13103             opt->min = min;
13104             opt->max = max;
13105             opt->type = Spin; // Slider;
13106         } else if((p = strstr(opt->name, " -string "))) {
13107             opt->textValue = p+9;
13108             opt->type = TextBox;
13109         } else if((p = strstr(opt->name, " -file "))) {
13110             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13111             opt->textValue = p+7;
13112             opt->type = TextBox; // FileName;
13113         } else if((p = strstr(opt->name, " -path "))) {
13114             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13115             opt->textValue = p+7;
13116             opt->type = TextBox; // PathName;
13117         } else if(p = strstr(opt->name, " -check ")) {
13118             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13119             opt->value = (def != 0);
13120             opt->type = CheckBox;
13121         } else if(p = strstr(opt->name, " -combo ")) {
13122             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13123             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13124             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13125             opt->value = n = 0;
13126             while(q = StrStr(q, " /// ")) {
13127                 n++; *q = 0;    // count choices, and null-terminate each of them
13128                 q += 5;
13129                 if(*q == '*') { // remember default, which is marked with * prefix
13130                     q++;
13131                     opt->value = n;
13132                 }
13133                 cps->comboList[cps->comboCnt++] = q;
13134             }
13135             cps->comboList[cps->comboCnt++] = NULL;
13136             opt->max = n + 1;
13137             opt->type = ComboBox;
13138         } else if(p = strstr(opt->name, " -button")) {
13139             opt->type = Button;
13140         } else if(p = strstr(opt->name, " -save")) {
13141             opt->type = SaveButton;
13142         } else return FALSE;
13143         *p = 0; // terminate option name
13144         // now look if the command-line options define a setting for this engine option.
13145         if(cps->optionSettings && cps->optionSettings[0])
13146             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13147         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13148                 sprintf(buf, "option %s", p);
13149                 if(p = strstr(buf, ",")) *p = 0;
13150                 strcat(buf, "\n");
13151                 SendToProgram(buf, cps);
13152         }
13153         return TRUE;
13154 }
13155
13156 void
13157 FeatureDone(cps, val)
13158      ChessProgramState* cps;
13159      int val;
13160 {
13161   DelayedEventCallback cb = GetDelayedEvent();
13162   if ((cb == InitBackEnd3 && cps == &first) ||
13163       (cb == TwoMachinesEventIfReady && cps == &second)) {
13164     CancelDelayedEvent();
13165     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13166   }
13167   cps->initDone = val;
13168 }
13169
13170 /* Parse feature command from engine */
13171 void
13172 ParseFeatures(args, cps)
13173      char* args;
13174      ChessProgramState *cps;
13175 {
13176   char *p = args;
13177   char *q;
13178   int val;
13179   char buf[MSG_SIZ];
13180
13181   for (;;) {
13182     while (*p == ' ') p++;
13183     if (*p == NULLCHAR) return;
13184
13185     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13186     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13187     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13188     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13189     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13190     if (BoolFeature(&p, "reuse", &val, cps)) {
13191       /* Engine can disable reuse, but can't enable it if user said no */
13192       if (!val) cps->reuse = FALSE;
13193       continue;
13194     }
13195     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13196     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13197       if (gameMode == TwoMachinesPlay) {
13198         DisplayTwoMachinesTitle();
13199       } else {
13200         DisplayTitle("");
13201       }
13202       continue;
13203     }
13204     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13205     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13206     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13207     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13208     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13209     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13210     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13211     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13212     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13213     if (IntFeature(&p, "done", &val, cps)) {
13214       FeatureDone(cps, val);
13215       continue;
13216     }
13217     /* Added by Tord: */
13218     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13219     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13220     /* End of additions by Tord */
13221
13222     /* [HGM] added features: */
13223     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13224     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13225     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13226     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13227     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13228     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13229     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13230         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13231             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13232             SendToProgram(buf, cps);
13233             continue;
13234         }
13235         if(cps->nrOptions >= MAX_OPTIONS) {
13236             cps->nrOptions--;
13237             sprintf(buf, "%s engine has too many options\n", cps->which);
13238             DisplayError(buf, 0);
13239         }
13240         continue;
13241     }
13242     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13243     /* End of additions by HGM */
13244
13245     /* unknown feature: complain and skip */
13246     q = p;
13247     while (*q && *q != '=') q++;
13248     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13249     SendToProgram(buf, cps);
13250     p = q;
13251     if (*p == '=') {
13252       p++;
13253       if (*p == '\"') {
13254         p++;
13255         while (*p && *p != '\"') p++;
13256         if (*p == '\"') p++;
13257       } else {
13258         while (*p && *p != ' ') p++;
13259       }
13260     }
13261   }
13262
13263 }
13264
13265 void
13266 PeriodicUpdatesEvent(newState)
13267      int newState;
13268 {
13269     if (newState == appData.periodicUpdates)
13270       return;
13271
13272     appData.periodicUpdates=newState;
13273
13274     /* Display type changes, so update it now */
13275 //    DisplayAnalysis();
13276
13277     /* Get the ball rolling again... */
13278     if (newState) {
13279         AnalysisPeriodicEvent(1);
13280         StartAnalysisClock();
13281     }
13282 }
13283
13284 void
13285 PonderNextMoveEvent(newState)
13286      int newState;
13287 {
13288     if (newState == appData.ponderNextMove) return;
13289     if (gameMode == EditPosition) EditPositionDone(TRUE);
13290     if (newState) {
13291         SendToProgram("hard\n", &first);
13292         if (gameMode == TwoMachinesPlay) {
13293             SendToProgram("hard\n", &second);
13294         }
13295     } else {
13296         SendToProgram("easy\n", &first);
13297         thinkOutput[0] = NULLCHAR;
13298         if (gameMode == TwoMachinesPlay) {
13299             SendToProgram("easy\n", &second);
13300         }
13301     }
13302     appData.ponderNextMove = newState;
13303 }
13304
13305 void
13306 NewSettingEvent(option, command, value)
13307      char *command;
13308      int option, value;
13309 {
13310     char buf[MSG_SIZ];
13311
13312     if (gameMode == EditPosition) EditPositionDone(TRUE);
13313     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13314     SendToProgram(buf, &first);
13315     if (gameMode == TwoMachinesPlay) {
13316         SendToProgram(buf, &second);
13317     }
13318 }
13319
13320 void
13321 ShowThinkingEvent()
13322 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13323 {
13324     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13325     int newState = appData.showThinking
13326         // [HGM] thinking: other features now need thinking output as well
13327         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13328
13329     if (oldState == newState) return;
13330     oldState = newState;
13331     if (gameMode == EditPosition) EditPositionDone(TRUE);
13332     if (oldState) {
13333         SendToProgram("post\n", &first);
13334         if (gameMode == TwoMachinesPlay) {
13335             SendToProgram("post\n", &second);
13336         }
13337     } else {
13338         SendToProgram("nopost\n", &first);
13339         thinkOutput[0] = NULLCHAR;
13340         if (gameMode == TwoMachinesPlay) {
13341             SendToProgram("nopost\n", &second);
13342         }
13343     }
13344 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13345 }
13346
13347 void
13348 AskQuestionEvent(title, question, replyPrefix, which)
13349      char *title; char *question; char *replyPrefix; char *which;
13350 {
13351   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13352   if (pr == NoProc) return;
13353   AskQuestion(title, question, replyPrefix, pr);
13354 }
13355
13356 void
13357 DisplayMove(moveNumber)
13358      int moveNumber;
13359 {
13360     char message[MSG_SIZ];
13361     char res[MSG_SIZ];
13362     char cpThinkOutput[MSG_SIZ];
13363
13364     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13365
13366     if (moveNumber == forwardMostMove - 1 ||
13367         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13368
13369         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13370
13371         if (strchr(cpThinkOutput, '\n')) {
13372             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13373         }
13374     } else {
13375         *cpThinkOutput = NULLCHAR;
13376     }
13377
13378     /* [AS] Hide thinking from human user */
13379     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13380         *cpThinkOutput = NULLCHAR;
13381         if( thinkOutput[0] != NULLCHAR ) {
13382             int i;
13383
13384             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13385                 cpThinkOutput[i] = '.';
13386             }
13387             cpThinkOutput[i] = NULLCHAR;
13388             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13389         }
13390     }
13391
13392     if (moveNumber == forwardMostMove - 1 &&
13393         gameInfo.resultDetails != NULL) {
13394         if (gameInfo.resultDetails[0] == NULLCHAR) {
13395             sprintf(res, " %s", PGNResult(gameInfo.result));
13396         } else {
13397             sprintf(res, " {%s} %s",
13398                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13399         }
13400     } else {
13401         res[0] = NULLCHAR;
13402     }
13403
13404     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13405         DisplayMessage(res, cpThinkOutput);
13406     } else {
13407         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13408                 WhiteOnMove(moveNumber) ? " " : ".. ",
13409                 parseList[moveNumber], res);
13410         DisplayMessage(message, cpThinkOutput);
13411     }
13412 }
13413
13414 void
13415 DisplayComment(moveNumber, text)
13416      int moveNumber;
13417      char *text;
13418 {
13419     char title[MSG_SIZ];
13420     char buf[8000]; // comment can be long!
13421     int score, depth;
13422     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13423       strcpy(title, "Comment");
13424     } else {
13425       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13426               WhiteOnMove(moveNumber) ? " " : ".. ",
13427               parseList[moveNumber]);
13428     }
13429     // [HGM] PV info: display PV info together with (or as) comment
13430     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13431       if(text == NULL) text = "";                                           
13432       score = pvInfoList[moveNumber].score;
13433       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13434               depth, (pvInfoList[moveNumber].time+50)/100, text);
13435       text = buf;
13436     }
13437     if (text != NULL && (appData.autoDisplayComment || commentUp))
13438       CommentPopUp(title, text);
13439 }
13440
13441 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13442  * might be busy thinking or pondering.  It can be omitted if your
13443  * gnuchess is configured to stop thinking immediately on any user
13444  * input.  However, that gnuchess feature depends on the FIONREAD
13445  * ioctl, which does not work properly on some flavors of Unix.
13446  */
13447 void
13448 Attention(cps)
13449      ChessProgramState *cps;
13450 {
13451 #if ATTENTION
13452     if (!cps->useSigint) return;
13453     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13454     switch (gameMode) {
13455       case MachinePlaysWhite:
13456       case MachinePlaysBlack:
13457       case TwoMachinesPlay:
13458       case IcsPlayingWhite:
13459       case IcsPlayingBlack:
13460       case AnalyzeMode:
13461       case AnalyzeFile:
13462         /* Skip if we know it isn't thinking */
13463         if (!cps->maybeThinking) return;
13464         if (appData.debugMode)
13465           fprintf(debugFP, "Interrupting %s\n", cps->which);
13466         InterruptChildProcess(cps->pr);
13467         cps->maybeThinking = FALSE;
13468         break;
13469       default:
13470         break;
13471     }
13472 #endif /*ATTENTION*/
13473 }
13474
13475 int
13476 CheckFlags()
13477 {
13478     if (whiteTimeRemaining <= 0) {
13479         if (!whiteFlag) {
13480             whiteFlag = TRUE;
13481             if (appData.icsActive) {
13482                 if (appData.autoCallFlag &&
13483                     gameMode == IcsPlayingBlack && !blackFlag) {
13484                   SendToICS(ics_prefix);
13485                   SendToICS("flag\n");
13486                 }
13487             } else {
13488                 if (blackFlag) {
13489                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13490                 } else {
13491                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13492                     if (appData.autoCallFlag) {
13493                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13494                         return TRUE;
13495                     }
13496                 }
13497             }
13498         }
13499     }
13500     if (blackTimeRemaining <= 0) {
13501         if (!blackFlag) {
13502             blackFlag = TRUE;
13503             if (appData.icsActive) {
13504                 if (appData.autoCallFlag &&
13505                     gameMode == IcsPlayingWhite && !whiteFlag) {
13506                   SendToICS(ics_prefix);
13507                   SendToICS("flag\n");
13508                 }
13509             } else {
13510                 if (whiteFlag) {
13511                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13512                 } else {
13513                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13514                     if (appData.autoCallFlag) {
13515                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13516                         return TRUE;
13517                     }
13518                 }
13519             }
13520         }
13521     }
13522     return FALSE;
13523 }
13524
13525 void
13526 CheckTimeControl()
13527 {
13528     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13529         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13530
13531     /*
13532      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13533      */
13534     if ( !WhiteOnMove(forwardMostMove) )
13535         /* White made time control */
13536         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13537         /* [HGM] time odds: correct new time quota for time odds! */
13538                                             / WhitePlayer()->timeOdds;
13539       else
13540         /* Black made time control */
13541         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13542                                             / WhitePlayer()->other->timeOdds;
13543 }
13544
13545 void
13546 DisplayBothClocks()
13547 {
13548     int wom = gameMode == EditPosition ?
13549       !blackPlaysFirst : WhiteOnMove(currentMove);
13550     DisplayWhiteClock(whiteTimeRemaining, wom);
13551     DisplayBlackClock(blackTimeRemaining, !wom);
13552 }
13553
13554
13555 /* Timekeeping seems to be a portability nightmare.  I think everyone
13556    has ftime(), but I'm really not sure, so I'm including some ifdefs
13557    to use other calls if you don't.  Clocks will be less accurate if
13558    you have neither ftime nor gettimeofday.
13559 */
13560
13561 /* VS 2008 requires the #include outside of the function */
13562 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13563 #include <sys/timeb.h>
13564 #endif
13565
13566 /* Get the current time as a TimeMark */
13567 void
13568 GetTimeMark(tm)
13569      TimeMark *tm;
13570 {
13571 #if HAVE_GETTIMEOFDAY
13572
13573     struct timeval timeVal;
13574     struct timezone timeZone;
13575
13576     gettimeofday(&timeVal, &timeZone);
13577     tm->sec = (long) timeVal.tv_sec;
13578     tm->ms = (int) (timeVal.tv_usec / 1000L);
13579
13580 #else /*!HAVE_GETTIMEOFDAY*/
13581 #if HAVE_FTIME
13582
13583 // include <sys/timeb.h> / moved to just above start of function
13584     struct timeb timeB;
13585
13586     ftime(&timeB);
13587     tm->sec = (long) timeB.time;
13588     tm->ms = (int) timeB.millitm;
13589
13590 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13591     tm->sec = (long) time(NULL);
13592     tm->ms = 0;
13593 #endif
13594 #endif
13595 }
13596
13597 /* Return the difference in milliseconds between two
13598    time marks.  We assume the difference will fit in a long!
13599 */
13600 long
13601 SubtractTimeMarks(tm2, tm1)
13602      TimeMark *tm2, *tm1;
13603 {
13604     return 1000L*(tm2->sec - tm1->sec) +
13605            (long) (tm2->ms - tm1->ms);
13606 }
13607
13608
13609 /*
13610  * Code to manage the game clocks.
13611  *
13612  * In tournament play, black starts the clock and then white makes a move.
13613  * We give the human user a slight advantage if he is playing white---the
13614  * clocks don't run until he makes his first move, so it takes zero time.
13615  * Also, we don't account for network lag, so we could get out of sync
13616  * with GNU Chess's clock -- but then, referees are always right.
13617  */
13618
13619 static TimeMark tickStartTM;
13620 static long intendedTickLength;
13621
13622 long
13623 NextTickLength(timeRemaining)
13624      long timeRemaining;
13625 {
13626     long nominalTickLength, nextTickLength;
13627
13628     if (timeRemaining > 0L && timeRemaining <= 10000L)
13629       nominalTickLength = 100L;
13630     else
13631       nominalTickLength = 1000L;
13632     nextTickLength = timeRemaining % nominalTickLength;
13633     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13634
13635     return nextTickLength;
13636 }
13637
13638 /* Adjust clock one minute up or down */
13639 void
13640 AdjustClock(Boolean which, int dir)
13641 {
13642     if(which) blackTimeRemaining += 60000*dir;
13643     else      whiteTimeRemaining += 60000*dir;
13644     DisplayBothClocks();
13645 }
13646
13647 /* Stop clocks and reset to a fresh time control */
13648 void
13649 ResetClocks()
13650 {
13651     (void) StopClockTimer();
13652     if (appData.icsActive) {
13653         whiteTimeRemaining = blackTimeRemaining = 0;
13654     } else if (searchTime) {
13655         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13656         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13657     } else { /* [HGM] correct new time quote for time odds */
13658         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13659         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13660     }
13661     if (whiteFlag || blackFlag) {
13662         DisplayTitle("");
13663         whiteFlag = blackFlag = FALSE;
13664     }
13665     DisplayBothClocks();
13666 }
13667
13668 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13669
13670 /* Decrement running clock by amount of time that has passed */
13671 void
13672 DecrementClocks()
13673 {
13674     long timeRemaining;
13675     long lastTickLength, fudge;
13676     TimeMark now;
13677
13678     if (!appData.clockMode) return;
13679     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13680
13681     GetTimeMark(&now);
13682
13683     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13684
13685     /* Fudge if we woke up a little too soon */
13686     fudge = intendedTickLength - lastTickLength;
13687     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13688
13689     if (WhiteOnMove(forwardMostMove)) {
13690         if(whiteNPS >= 0) lastTickLength = 0;
13691         timeRemaining = whiteTimeRemaining -= lastTickLength;
13692         DisplayWhiteClock(whiteTimeRemaining - fudge,
13693                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13694     } else {
13695         if(blackNPS >= 0) lastTickLength = 0;
13696         timeRemaining = blackTimeRemaining -= lastTickLength;
13697         DisplayBlackClock(blackTimeRemaining - fudge,
13698                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13699     }
13700
13701     if (CheckFlags()) return;
13702
13703     tickStartTM = now;
13704     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13705     StartClockTimer(intendedTickLength);
13706
13707     /* if the time remaining has fallen below the alarm threshold, sound the
13708      * alarm. if the alarm has sounded and (due to a takeback or time control
13709      * with increment) the time remaining has increased to a level above the
13710      * threshold, reset the alarm so it can sound again.
13711      */
13712
13713     if (appData.icsActive && appData.icsAlarm) {
13714
13715         /* make sure we are dealing with the user's clock */
13716         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13717                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13718            )) return;
13719
13720         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13721             alarmSounded = FALSE;
13722         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13723             PlayAlarmSound();
13724             alarmSounded = TRUE;
13725         }
13726     }
13727 }
13728
13729
13730 /* A player has just moved, so stop the previously running
13731    clock and (if in clock mode) start the other one.
13732    We redisplay both clocks in case we're in ICS mode, because
13733    ICS gives us an update to both clocks after every move.
13734    Note that this routine is called *after* forwardMostMove
13735    is updated, so the last fractional tick must be subtracted
13736    from the color that is *not* on move now.
13737 */
13738 void
13739 SwitchClocks()
13740 {
13741     long lastTickLength;
13742     TimeMark now;
13743     int flagged = FALSE;
13744
13745     GetTimeMark(&now);
13746
13747     if (StopClockTimer() && appData.clockMode) {
13748         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13749         if (WhiteOnMove(forwardMostMove)) {
13750             if(blackNPS >= 0) lastTickLength = 0;
13751             blackTimeRemaining -= lastTickLength;
13752            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13753 //         if(pvInfoList[forwardMostMove-1].time == -1)
13754                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13755                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13756         } else {
13757            if(whiteNPS >= 0) lastTickLength = 0;
13758            whiteTimeRemaining -= lastTickLength;
13759            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13760 //         if(pvInfoList[forwardMostMove-1].time == -1)
13761                  pvInfoList[forwardMostMove-1].time =
13762                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13763         }
13764         flagged = CheckFlags();
13765     }
13766     CheckTimeControl();
13767
13768     if (flagged || !appData.clockMode) return;
13769
13770     switch (gameMode) {
13771       case MachinePlaysBlack:
13772       case MachinePlaysWhite:
13773       case BeginningOfGame:
13774         if (pausing) return;
13775         break;
13776
13777       case EditGame:
13778       case PlayFromGameFile:
13779       case IcsExamining:
13780         return;
13781
13782       default:
13783         break;
13784     }
13785
13786     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13787         if(WhiteOnMove(forwardMostMove))
13788              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13789         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13790     }
13791
13792     tickStartTM = now;
13793     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13794       whiteTimeRemaining : blackTimeRemaining);
13795     StartClockTimer(intendedTickLength);
13796 }
13797
13798
13799 /* Stop both clocks */
13800 void
13801 StopClocks()
13802 {
13803     long lastTickLength;
13804     TimeMark now;
13805
13806     if (!StopClockTimer()) return;
13807     if (!appData.clockMode) return;
13808
13809     GetTimeMark(&now);
13810
13811     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13812     if (WhiteOnMove(forwardMostMove)) {
13813         if(whiteNPS >= 0) lastTickLength = 0;
13814         whiteTimeRemaining -= lastTickLength;
13815         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13816     } else {
13817         if(blackNPS >= 0) lastTickLength = 0;
13818         blackTimeRemaining -= lastTickLength;
13819         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13820     }
13821     CheckFlags();
13822 }
13823
13824 /* Start clock of player on move.  Time may have been reset, so
13825    if clock is already running, stop and restart it. */
13826 void
13827 StartClocks()
13828 {
13829     (void) StopClockTimer(); /* in case it was running already */
13830     DisplayBothClocks();
13831     if (CheckFlags()) return;
13832
13833     if (!appData.clockMode) return;
13834     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13835
13836     GetTimeMark(&tickStartTM);
13837     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13838       whiteTimeRemaining : blackTimeRemaining);
13839
13840    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13841     whiteNPS = blackNPS = -1;
13842     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13843        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13844         whiteNPS = first.nps;
13845     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13846        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13847         blackNPS = first.nps;
13848     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13849         whiteNPS = second.nps;
13850     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13851         blackNPS = second.nps;
13852     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13853
13854     StartClockTimer(intendedTickLength);
13855 }
13856
13857 char *
13858 TimeString(ms)
13859      long ms;
13860 {
13861     long second, minute, hour, day;
13862     char *sign = "";
13863     static char buf[32];
13864
13865     if (ms > 0 && ms <= 9900) {
13866       /* convert milliseconds to tenths, rounding up */
13867       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13868
13869       sprintf(buf, " %03.1f ", tenths/10.0);
13870       return buf;
13871     }
13872
13873     /* convert milliseconds to seconds, rounding up */
13874     /* use floating point to avoid strangeness of integer division
13875        with negative dividends on many machines */
13876     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13877
13878     if (second < 0) {
13879         sign = "-";
13880         second = -second;
13881     }
13882
13883     day = second / (60 * 60 * 24);
13884     second = second % (60 * 60 * 24);
13885     hour = second / (60 * 60);
13886     second = second % (60 * 60);
13887     minute = second / 60;
13888     second = second % 60;
13889
13890     if (day > 0)
13891       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13892               sign, day, hour, minute, second);
13893     else if (hour > 0)
13894       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13895     else
13896       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13897
13898     return buf;
13899 }
13900
13901
13902 /*
13903  * This is necessary because some C libraries aren't ANSI C compliant yet.
13904  */
13905 char *
13906 StrStr(string, match)
13907      char *string, *match;
13908 {
13909     int i, length;
13910
13911     length = strlen(match);
13912
13913     for (i = strlen(string) - length; i >= 0; i--, string++)
13914       if (!strncmp(match, string, length))
13915         return string;
13916
13917     return NULL;
13918 }
13919
13920 char *
13921 StrCaseStr(string, match)
13922      char *string, *match;
13923 {
13924     int i, j, length;
13925
13926     length = strlen(match);
13927
13928     for (i = strlen(string) - length; i >= 0; i--, string++) {
13929         for (j = 0; j < length; j++) {
13930             if (ToLower(match[j]) != ToLower(string[j]))
13931               break;
13932         }
13933         if (j == length) return string;
13934     }
13935
13936     return NULL;
13937 }
13938
13939 #ifndef _amigados
13940 int
13941 StrCaseCmp(s1, s2)
13942      char *s1, *s2;
13943 {
13944     char c1, c2;
13945
13946     for (;;) {
13947         c1 = ToLower(*s1++);
13948         c2 = ToLower(*s2++);
13949         if (c1 > c2) return 1;
13950         if (c1 < c2) return -1;
13951         if (c1 == NULLCHAR) return 0;
13952     }
13953 }
13954
13955
13956 int
13957 ToLower(c)
13958      int c;
13959 {
13960     return isupper(c) ? tolower(c) : c;
13961 }
13962
13963
13964 int
13965 ToUpper(c)
13966      int c;
13967 {
13968     return islower(c) ? toupper(c) : c;
13969 }
13970 #endif /* !_amigados    */
13971
13972 char *
13973 StrSave(s)
13974      char *s;
13975 {
13976     char *ret;
13977
13978     if ((ret = (char *) malloc(strlen(s) + 1))) {
13979         strcpy(ret, s);
13980     }
13981     return ret;
13982 }
13983
13984 char *
13985 StrSavePtr(s, savePtr)
13986      char *s, **savePtr;
13987 {
13988     if (*savePtr) {
13989         free(*savePtr);
13990     }
13991     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13992         strcpy(*savePtr, s);
13993     }
13994     return(*savePtr);
13995 }
13996
13997 char *
13998 PGNDate()
13999 {
14000     time_t clock;
14001     struct tm *tm;
14002     char buf[MSG_SIZ];
14003
14004     clock = time((time_t *)NULL);
14005     tm = localtime(&clock);
14006     sprintf(buf, "%04d.%02d.%02d",
14007             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14008     return StrSave(buf);
14009 }
14010
14011
14012 char *
14013 PositionToFEN(move, overrideCastling)
14014      int move;
14015      char *overrideCastling;
14016 {
14017     int i, j, fromX, fromY, toX, toY;
14018     int whiteToPlay;
14019     char buf[128];
14020     char *p, *q;
14021     int emptycount;
14022     ChessSquare piece;
14023
14024     whiteToPlay = (gameMode == EditPosition) ?
14025       !blackPlaysFirst : (move % 2 == 0);
14026     p = buf;
14027
14028     /* Piece placement data */
14029     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14030         emptycount = 0;
14031         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14032             if (boards[move][i][j] == EmptySquare) {
14033                 emptycount++;
14034             } else { ChessSquare piece = boards[move][i][j];
14035                 if (emptycount > 0) {
14036                     if(emptycount<10) /* [HGM] can be >= 10 */
14037                         *p++ = '0' + emptycount;
14038                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14039                     emptycount = 0;
14040                 }
14041                 if(PieceToChar(piece) == '+') {
14042                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14043                     *p++ = '+';
14044                     piece = (ChessSquare)(DEMOTED piece);
14045                 }
14046                 *p++ = PieceToChar(piece);
14047                 if(p[-1] == '~') {
14048                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14049                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14050                     *p++ = '~';
14051                 }
14052             }
14053         }
14054         if (emptycount > 0) {
14055             if(emptycount<10) /* [HGM] can be >= 10 */
14056                 *p++ = '0' + emptycount;
14057             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14058             emptycount = 0;
14059         }
14060         *p++ = '/';
14061     }
14062     *(p - 1) = ' ';
14063
14064     /* [HGM] print Crazyhouse or Shogi holdings */
14065     if( gameInfo.holdingsWidth ) {
14066         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14067         q = p;
14068         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14069             piece = boards[move][i][BOARD_WIDTH-1];
14070             if( piece != EmptySquare )
14071               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14072                   *p++ = PieceToChar(piece);
14073         }
14074         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14075             piece = boards[move][BOARD_HEIGHT-i-1][0];
14076             if( piece != EmptySquare )
14077               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14078                   *p++ = PieceToChar(piece);
14079         }
14080
14081         if( q == p ) *p++ = '-';
14082         *p++ = ']';
14083         *p++ = ' ';
14084     }
14085
14086     /* Active color */
14087     *p++ = whiteToPlay ? 'w' : 'b';
14088     *p++ = ' ';
14089
14090   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14091     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14092   } else {
14093   if(nrCastlingRights) {
14094      q = p;
14095      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14096        /* [HGM] write directly from rights */
14097            if(boards[move][CASTLING][2] != NoRights &&
14098               boards[move][CASTLING][0] != NoRights   )
14099                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14100            if(boards[move][CASTLING][2] != NoRights &&
14101               boards[move][CASTLING][1] != NoRights   )
14102                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14103            if(boards[move][CASTLING][5] != NoRights &&
14104               boards[move][CASTLING][3] != NoRights   )
14105                 *p++ = boards[move][CASTLING][3] + AAA;
14106            if(boards[move][CASTLING][5] != NoRights &&
14107               boards[move][CASTLING][4] != NoRights   )
14108                 *p++ = boards[move][CASTLING][4] + AAA;
14109      } else {
14110
14111         /* [HGM] write true castling rights */
14112         if( nrCastlingRights == 6 ) {
14113             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14114                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14115             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14116                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14117             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14118                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14119             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14120                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14121         }
14122      }
14123      if (q == p) *p++ = '-'; /* No castling rights */
14124      *p++ = ' ';
14125   }
14126
14127   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14128      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14129     /* En passant target square */
14130     if (move > backwardMostMove) {
14131         fromX = moveList[move - 1][0] - AAA;
14132         fromY = moveList[move - 1][1] - ONE;
14133         toX = moveList[move - 1][2] - AAA;
14134         toY = moveList[move - 1][3] - ONE;
14135         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14136             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14137             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14138             fromX == toX) {
14139             /* 2-square pawn move just happened */
14140             *p++ = toX + AAA;
14141             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14142         } else {
14143             *p++ = '-';
14144         }
14145     } else if(move == backwardMostMove) {
14146         // [HGM] perhaps we should always do it like this, and forget the above?
14147         if((signed char)boards[move][EP_STATUS] >= 0) {
14148             *p++ = boards[move][EP_STATUS] + AAA;
14149             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14150         } else {
14151             *p++ = '-';
14152         }
14153     } else {
14154         *p++ = '-';
14155     }
14156     *p++ = ' ';
14157   }
14158   }
14159
14160     /* [HGM] find reversible plies */
14161     {   int i = 0, j=move;
14162
14163         if (appData.debugMode) { int k;
14164             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14165             for(k=backwardMostMove; k<=forwardMostMove; k++)
14166                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14167
14168         }
14169
14170         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14171         if( j == backwardMostMove ) i += initialRulePlies;
14172         sprintf(p, "%d ", i);
14173         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14174     }
14175     /* Fullmove number */
14176     sprintf(p, "%d", (move / 2) + 1);
14177
14178     return StrSave(buf);
14179 }
14180
14181 Boolean
14182 ParseFEN(board, blackPlaysFirst, fen)
14183     Board board;
14184      int *blackPlaysFirst;
14185      char *fen;
14186 {
14187     int i, j;
14188     char *p;
14189     int emptycount;
14190     ChessSquare piece;
14191
14192     p = fen;
14193
14194     /* [HGM] by default clear Crazyhouse holdings, if present */
14195     if(gameInfo.holdingsWidth) {
14196        for(i=0; i<BOARD_HEIGHT; i++) {
14197            board[i][0]             = EmptySquare; /* black holdings */
14198            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14199            board[i][1]             = (ChessSquare) 0; /* black counts */
14200            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14201        }
14202     }
14203
14204     /* Piece placement data */
14205     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14206         j = 0;
14207         for (;;) {
14208             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14209                 if (*p == '/') p++;
14210                 emptycount = gameInfo.boardWidth - j;
14211                 while (emptycount--)
14212                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14213                 break;
14214 #if(BOARD_FILES >= 10)
14215             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14216                 p++; emptycount=10;
14217                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14218                 while (emptycount--)
14219                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14220 #endif
14221             } else if (isdigit(*p)) {
14222                 emptycount = *p++ - '0';
14223                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14224                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14225                 while (emptycount--)
14226                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14227             } else if (*p == '+' || isalpha(*p)) {
14228                 if (j >= gameInfo.boardWidth) return FALSE;
14229                 if(*p=='+') {
14230                     piece = CharToPiece(*++p);
14231                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14232                     piece = (ChessSquare) (PROMOTED piece ); p++;
14233                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14234                 } else piece = CharToPiece(*p++);
14235
14236                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14237                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14238                     piece = (ChessSquare) (PROMOTED piece);
14239                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14240                     p++;
14241                 }
14242                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14243             } else {
14244                 return FALSE;
14245             }
14246         }
14247     }
14248     while (*p == '/' || *p == ' ') p++;
14249
14250     /* [HGM] look for Crazyhouse holdings here */
14251     while(*p==' ') p++;
14252     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14253         if(*p == '[') p++;
14254         if(*p == '-' ) *p++; /* empty holdings */ else {
14255             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14256             /* if we would allow FEN reading to set board size, we would   */
14257             /* have to add holdings and shift the board read so far here   */
14258             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14259                 *p++;
14260                 if((int) piece >= (int) BlackPawn ) {
14261                     i = (int)piece - (int)BlackPawn;
14262                     i = PieceToNumber((ChessSquare)i);
14263                     if( i >= gameInfo.holdingsSize ) return FALSE;
14264                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14265                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14266                 } else {
14267                     i = (int)piece - (int)WhitePawn;
14268                     i = PieceToNumber((ChessSquare)i);
14269                     if( i >= gameInfo.holdingsSize ) return FALSE;
14270                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14271                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14272                 }
14273             }
14274         }
14275         if(*p == ']') *p++;
14276     }
14277
14278     while(*p == ' ') p++;
14279
14280     /* Active color */
14281     switch (*p++) {
14282       case 'w':
14283         *blackPlaysFirst = FALSE;
14284         break;
14285       case 'b':
14286         *blackPlaysFirst = TRUE;
14287         break;
14288       default:
14289         return FALSE;
14290     }
14291
14292     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14293     /* return the extra info in global variiables             */
14294
14295     /* set defaults in case FEN is incomplete */
14296     board[EP_STATUS] = EP_UNKNOWN;
14297     for(i=0; i<nrCastlingRights; i++ ) {
14298         board[CASTLING][i] =
14299             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14300     }   /* assume possible unless obviously impossible */
14301     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14302     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14303     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14304                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14305     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14306     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14307     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14308                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14309     FENrulePlies = 0;
14310
14311     while(*p==' ') p++;
14312     if(nrCastlingRights) {
14313       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14314           /* castling indicator present, so default becomes no castlings */
14315           for(i=0; i<nrCastlingRights; i++ ) {
14316                  board[CASTLING][i] = NoRights;
14317           }
14318       }
14319       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14320              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14321              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14322              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14323         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14324
14325         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14326             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14327             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14328         }
14329         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14330             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14331         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14332                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14333         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14334                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14335         switch(c) {
14336           case'K':
14337               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14338               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14339               board[CASTLING][2] = whiteKingFile;
14340               break;
14341           case'Q':
14342               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14343               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14344               board[CASTLING][2] = whiteKingFile;
14345               break;
14346           case'k':
14347               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14348               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14349               board[CASTLING][5] = blackKingFile;
14350               break;
14351           case'q':
14352               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14353               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14354               board[CASTLING][5] = blackKingFile;
14355           case '-':
14356               break;
14357           default: /* FRC castlings */
14358               if(c >= 'a') { /* black rights */
14359                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14360                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14361                   if(i == BOARD_RGHT) break;
14362                   board[CASTLING][5] = i;
14363                   c -= AAA;
14364                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14365                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14366                   if(c > i)
14367                       board[CASTLING][3] = c;
14368                   else
14369                       board[CASTLING][4] = c;
14370               } else { /* white rights */
14371                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14372                     if(board[0][i] == WhiteKing) break;
14373                   if(i == BOARD_RGHT) break;
14374                   board[CASTLING][2] = i;
14375                   c -= AAA - 'a' + 'A';
14376                   if(board[0][c] >= WhiteKing) break;
14377                   if(c > i)
14378                       board[CASTLING][0] = c;
14379                   else
14380                       board[CASTLING][1] = c;
14381               }
14382         }
14383       }
14384       for(i=0; i<nrCastlingRights; i++)
14385         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14386     if (appData.debugMode) {
14387         fprintf(debugFP, "FEN castling rights:");
14388         for(i=0; i<nrCastlingRights; i++)
14389         fprintf(debugFP, " %d", board[CASTLING][i]);
14390         fprintf(debugFP, "\n");
14391     }
14392
14393       while(*p==' ') p++;
14394     }
14395
14396     /* read e.p. field in games that know e.p. capture */
14397     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14398        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14399       if(*p=='-') {
14400         p++; board[EP_STATUS] = EP_NONE;
14401       } else {
14402          char c = *p++ - AAA;
14403
14404          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14405          if(*p >= '0' && *p <='9') *p++;
14406          board[EP_STATUS] = c;
14407       }
14408     }
14409
14410
14411     if(sscanf(p, "%d", &i) == 1) {
14412         FENrulePlies = i; /* 50-move ply counter */
14413         /* (The move number is still ignored)    */
14414     }
14415
14416     return TRUE;
14417 }
14418
14419 void
14420 EditPositionPasteFEN(char *fen)
14421 {
14422   if (fen != NULL) {
14423     Board initial_position;
14424
14425     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14426       DisplayError(_("Bad FEN position in clipboard"), 0);
14427       return ;
14428     } else {
14429       int savedBlackPlaysFirst = blackPlaysFirst;
14430       EditPositionEvent();
14431       blackPlaysFirst = savedBlackPlaysFirst;
14432       CopyBoard(boards[0], initial_position);
14433       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14434       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14435       DisplayBothClocks();
14436       DrawPosition(FALSE, boards[currentMove]);
14437     }
14438   }
14439 }
14440
14441 static char cseq[12] = "\\   ";
14442
14443 Boolean set_cont_sequence(char *new_seq)
14444 {
14445     int len;
14446     Boolean ret;
14447
14448     // handle bad attempts to set the sequence
14449         if (!new_seq)
14450                 return 0; // acceptable error - no debug
14451
14452     len = strlen(new_seq);
14453     ret = (len > 0) && (len < sizeof(cseq));
14454     if (ret)
14455         strcpy(cseq, new_seq);
14456     else if (appData.debugMode)
14457         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14458     return ret;
14459 }
14460
14461 /*
14462     reformat a source message so words don't cross the width boundary.  internal
14463     newlines are not removed.  returns the wrapped size (no null character unless
14464     included in source message).  If dest is NULL, only calculate the size required
14465     for the dest buffer.  lp argument indicats line position upon entry, and it's
14466     passed back upon exit.
14467 */
14468 int wrap(char *dest, char *src, int count, int width, int *lp)
14469 {
14470     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14471
14472     cseq_len = strlen(cseq);
14473     old_line = line = *lp;
14474     ansi = len = clen = 0;
14475
14476     for (i=0; i < count; i++)
14477     {
14478         if (src[i] == '\033')
14479             ansi = 1;
14480
14481         // if we hit the width, back up
14482         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14483         {
14484             // store i & len in case the word is too long
14485             old_i = i, old_len = len;
14486
14487             // find the end of the last word
14488             while (i && src[i] != ' ' && src[i] != '\n')
14489             {
14490                 i--;
14491                 len--;
14492             }
14493
14494             // word too long?  restore i & len before splitting it
14495             if ((old_i-i+clen) >= width)
14496             {
14497                 i = old_i;
14498                 len = old_len;
14499             }
14500
14501             // extra space?
14502             if (i && src[i-1] == ' ')
14503                 len--;
14504
14505             if (src[i] != ' ' && src[i] != '\n')
14506             {
14507                 i--;
14508                 if (len)
14509                     len--;
14510             }
14511
14512             // now append the newline and continuation sequence
14513             if (dest)
14514                 dest[len] = '\n';
14515             len++;
14516             if (dest)
14517                 strncpy(dest+len, cseq, cseq_len);
14518             len += cseq_len;
14519             line = cseq_len;
14520             clen = cseq_len;
14521             continue;
14522         }
14523
14524         if (dest)
14525             dest[len] = src[i];
14526         len++;
14527         if (!ansi)
14528             line++;
14529         if (src[i] == '\n')
14530             line = 0;
14531         if (src[i] == 'm')
14532             ansi = 0;
14533     }
14534     if (dest && appData.debugMode)
14535     {
14536         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14537             count, width, line, len, *lp);
14538         show_bytes(debugFP, src, count);
14539         fprintf(debugFP, "\ndest: ");
14540         show_bytes(debugFP, dest, len);
14541         fprintf(debugFP, "\n");
14542     }
14543     *lp = dest ? line : old_line;
14544
14545     return len;
14546 }
14547
14548 // [HGM] vari: routines for shelving variations
14549
14550 void 
14551 PushTail(int firstMove, int lastMove)
14552 {
14553         int i, j, nrMoves = lastMove - firstMove;
14554
14555         if(appData.icsActive) { // only in local mode
14556                 forwardMostMove = currentMove; // mimic old ICS behavior
14557                 return;
14558         }
14559         if(storedGames >= MAX_VARIATIONS-1) return;
14560
14561         // push current tail of game on stack
14562         savedResult[storedGames] = gameInfo.result;
14563         savedDetails[storedGames] = gameInfo.resultDetails;
14564         gameInfo.resultDetails = NULL;
14565         savedFirst[storedGames] = firstMove;
14566         savedLast [storedGames] = lastMove;
14567         savedFramePtr[storedGames] = framePtr;
14568         framePtr -= nrMoves; // reserve space for the boards
14569         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14570             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14571             for(j=0; j<MOVE_LEN; j++)
14572                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14573             for(j=0; j<2*MOVE_LEN; j++)
14574                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14575             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14576             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14577             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14578             pvInfoList[firstMove+i-1].depth = 0;
14579             commentList[framePtr+i] = commentList[firstMove+i];
14580             commentList[firstMove+i] = NULL;
14581         }
14582
14583         storedGames++;
14584         forwardMostMove = currentMove; // truncte game so we can start variation
14585         if(storedGames == 1) GreyRevert(FALSE);
14586 }
14587
14588 Boolean
14589 PopTail(Boolean annotate)
14590 {
14591         int i, j, nrMoves;
14592         char buf[8000], moveBuf[20];
14593
14594         if(appData.icsActive) return FALSE; // only in local mode
14595         if(!storedGames) return FALSE; // sanity
14596
14597         storedGames--;
14598         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14599         nrMoves = savedLast[storedGames] - currentMove;
14600         if(annotate) {
14601                 int cnt = 10;
14602                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14603                 else strcpy(buf, "(");
14604                 for(i=currentMove; i<forwardMostMove; i++) {
14605                         if(WhiteOnMove(i))
14606                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14607                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14608                         strcat(buf, moveBuf);
14609                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14610                 }
14611                 strcat(buf, ")");
14612         }
14613         for(i=1; i<nrMoves; i++) { // copy last variation back
14614             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14615             for(j=0; j<MOVE_LEN; j++)
14616                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14617             for(j=0; j<2*MOVE_LEN; j++)
14618                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14619             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14620             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14621             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14622             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14623             commentList[currentMove+i] = commentList[framePtr+i];
14624             commentList[framePtr+i] = NULL;
14625         }
14626         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14627         framePtr = savedFramePtr[storedGames];
14628         gameInfo.result = savedResult[storedGames];
14629         if(gameInfo.resultDetails != NULL) {
14630             free(gameInfo.resultDetails);
14631       }
14632         gameInfo.resultDetails = savedDetails[storedGames];
14633         forwardMostMove = currentMove + nrMoves;
14634         if(storedGames == 0) GreyRevert(TRUE);
14635         return TRUE;
14636 }
14637
14638 void 
14639 CleanupTail()
14640 {       // remove all shelved variations
14641         int i;
14642         for(i=0; i<storedGames; i++) {
14643             if(savedDetails[i])
14644                 free(savedDetails[i]);
14645             savedDetails[i] = NULL;
14646         }
14647         for(i=framePtr; i<MAX_MOVES; i++) {
14648                 if(commentList[i]) free(commentList[i]);
14649                 commentList[i] = NULL;
14650         }
14651         framePtr = MAX_MOVES-1;
14652         storedGames = 0;
14653 }