e686722413ddcb48bbfb09cfb12875d035e2d800
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 { // [HGM] made safe
315   int i;
316   assert( dst != NULL );
317   assert( src != NULL );
318   assert( count > 0 );
319
320   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321   if(  i == count && dst[count-1] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
326     }
327
328   return dst;
329 }
330
331 /* Some compiler can't cast u64 to double
332  * This function do the job for us:
333
334  * We use the highest bit for cast, this only
335  * works if the highest bit is not
336  * in use (This should not happen)
337  *
338  * We used this for all compiler
339  */
340 double
341 u64ToDouble(u64 value)
342 {
343   double r;
344   u64 tmp = value & u64Const(0x7fffffffffffffff);
345   r = (double)(s64)tmp;
346   if (value & u64Const(0x8000000000000000))
347        r +=  9.2233720368547758080e18; /* 2^63 */
348  return r;
349 }
350
351 /* Fake up flags for now, as we aren't keeping track of castling
352    availability yet. [HGM] Change of logic: the flag now only
353    indicates the type of castlings allowed by the rule of the game.
354    The actual rights themselves are maintained in the array
355    castlingRights, as part of the game history, and are not probed
356    by this function.
357  */
358 int
359 PosFlags(index)
360 {
361   int flags = F_ALL_CASTLE_OK;
362   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363   switch (gameInfo.variant) {
364   case VariantSuicide:
365     flags &= ~F_ALL_CASTLE_OK;
366   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367     flags |= F_IGNORE_CHECK;
368   case VariantLosers:
369     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
370     break;
371   case VariantAtomic:
372     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
373     break;
374   case VariantKriegspiel:
375     flags |= F_KRIEGSPIEL_CAPTURE;
376     break;
377   case VariantCapaRandom:
378   case VariantFischeRandom:
379     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380   case VariantNoCastle:
381   case VariantShatranj:
382   case VariantCourier:
383   case VariantMakruk:
384     flags &= ~F_ALL_CASTLE_OK;
385     break;
386   default:
387     break;
388   }
389   return flags;
390 }
391
392 FILE *gameFileFP, *debugFP;
393
394 /*
395     [AS] Note: sometimes, the sscanf() function is used to parse the input
396     into a fixed-size buffer. Because of this, we must be prepared to
397     receive strings as long as the size of the input buffer, which is currently
398     set to 4K for Windows and 8K for the rest.
399     So, we must either allocate sufficiently large buffers here, or
400     reduce the size of the input buffer in the input reading part.
401 */
402
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
406
407 ChessProgramState first, second;
408
409 /* premove variables */
410 int premoveToX = 0;
411 int premoveToY = 0;
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
415 int gotPremove = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
418
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
421
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
449
450 int have_sent_ICS_logon = 0;
451 int movesPerSession;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
457 int matchGame = 0;
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
461
462 /* animateTraining preserves the state of appData.animate
463  * when Training mode is activated. This allows the
464  * response to be animated when appData.animate == TRUE and
465  * appData.animateDragging == TRUE.
466  */
467 Boolean animateTraining;
468
469 GameInfo gameInfo;
470
471 AppData appData;
472
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char  initialRights[BOARD_FILES];
477 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int   initialRulePlies, FENrulePlies;
479 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
480 int loadFlag = 0;
481 int shuffleOpenings;
482 int mute; // mute all sounds
483
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
487 int storedGames = 0;
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
493
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
497
498 ChessSquare  FIDEArray[2][BOARD_FILES] = {
499     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502         BlackKing, BlackBishop, BlackKnight, BlackRook }
503 };
504
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackKing, BlackKnight, BlackRook }
510 };
511
512 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515     { BlackRook, BlackMan, BlackBishop, BlackQueen,
516         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
517 };
518
519 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
523         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
524 };
525
526 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
527     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
528         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
529     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
530         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
531 };
532
533 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
535         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackMan, BlackFerz,
537         BlackKing, BlackMan, BlackKnight, BlackRook }
538 };
539
540
541 #if (BOARD_FILES>=10)
542 ChessSquare ShogiArray[2][BOARD_FILES] = {
543     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
544         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
545     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
546         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
547 };
548
549 ChessSquare XiangqiArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
551         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
553         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare CapablancaArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
558         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
560         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
561 };
562
563 ChessSquare GreatArray[2][BOARD_FILES] = {
564     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
565         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
566     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
567         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
568 };
569
570 ChessSquare JanusArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
572         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
573     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
574         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
575 };
576
577 #ifdef GOTHIC
578 ChessSquare GothicArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
580         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
582         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
583 };
584 #else // !GOTHIC
585 #define GothicArray CapablancaArray
586 #endif // !GOTHIC
587
588 #ifdef FALCON
589 ChessSquare FalconArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
591         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
593         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
594 };
595 #else // !FALCON
596 #define FalconArray CapablancaArray
597 #endif // !FALCON
598
599 #else // !(BOARD_FILES>=10)
600 #define XiangqiPosition FIDEArray
601 #define CapablancaArray FIDEArray
602 #define GothicArray FIDEArray
603 #define GreatArray FIDEArray
604 #endif // !(BOARD_FILES>=10)
605
606 #if (BOARD_FILES>=12)
607 ChessSquare CourierArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
609         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
610     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
611         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
612 };
613 #else // !(BOARD_FILES>=12)
614 #define CourierArray CapablancaArray
615 #endif // !(BOARD_FILES>=12)
616
617
618 Board initialPosition;
619
620
621 /* Convert str to a rating. Checks for special cases of "----",
622
623    "++++", etc. Also strips ()'s */
624 int
625 string_to_rating(str)
626   char *str;
627 {
628   while(*str && !isdigit(*str)) ++str;
629   if (!*str)
630     return 0;   /* One of the special "no rating" cases */
631   else
632     return atoi(str);
633 }
634
635 void
636 ClearProgramStats()
637 {
638     /* Init programStats */
639     programStats.movelist[0] = 0;
640     programStats.depth = 0;
641     programStats.nr_moves = 0;
642     programStats.moves_left = 0;
643     programStats.nodes = 0;
644     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
645     programStats.score = 0;
646     programStats.got_only_move = 0;
647     programStats.got_fail = 0;
648     programStats.line_is_book = 0;
649 }
650
651 void
652 InitBackEnd1()
653 {
654     int matched, min, sec;
655
656     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
657     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
658
659     GetTimeMark(&programStartTime);
660     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
661
662     ClearProgramStats();
663     programStats.ok_to_send = 1;
664     programStats.seen_stat = 0;
665
666     /*
667      * Initialize game list
668      */
669     ListNew(&gameList);
670
671
672     /*
673      * Internet chess server status
674      */
675     if (appData.icsActive) {
676         appData.matchMode = FALSE;
677         appData.matchGames = 0;
678 #if ZIPPY
679         appData.noChessProgram = !appData.zippyPlay;
680 #else
681         appData.zippyPlay = FALSE;
682         appData.zippyTalk = FALSE;
683         appData.noChessProgram = TRUE;
684 #endif
685         if (*appData.icsHelper != NULLCHAR) {
686             appData.useTelnet = TRUE;
687             appData.telnetProgram = appData.icsHelper;
688         }
689     } else {
690         appData.zippyTalk = appData.zippyPlay = FALSE;
691     }
692
693     /* [AS] Initialize pv info list [HGM] and game state */
694     {
695         int i, j;
696
697         for( i=0; i<=framePtr; i++ ) {
698             pvInfoList[i].depth = -1;
699             boards[i][EP_STATUS] = EP_NONE;
700             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
701         }
702     }
703
704     /*
705      * Parse timeControl resource
706      */
707     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
708                           appData.movesPerSession)) {
709         char buf[MSG_SIZ];
710         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
711         DisplayFatalError(buf, 0, 2);
712     }
713
714     /*
715      * Parse searchTime resource
716      */
717     if (*appData.searchTime != NULLCHAR) {
718         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
719         if (matched == 1) {
720             searchTime = min * 60;
721         } else if (matched == 2) {
722             searchTime = min * 60 + sec;
723         } else {
724             char buf[MSG_SIZ];
725             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
726             DisplayFatalError(buf, 0, 2);
727         }
728     }
729
730     /* [AS] Adjudication threshold */
731     adjudicateLossThreshold = appData.adjudicateLossThreshold;
732
733     first.which = _("first");
734     second.which = _("second");
735     first.maybeThinking = second.maybeThinking = FALSE;
736     first.pr = second.pr = NoProc;
737     first.isr = second.isr = NULL;
738     first.sendTime = second.sendTime = 2;
739     first.sendDrawOffers = 1;
740     if (appData.firstPlaysBlack) {
741         first.twoMachinesColor = "black\n";
742         second.twoMachinesColor = "white\n";
743     } else {
744         first.twoMachinesColor = "white\n";
745         second.twoMachinesColor = "black\n";
746     }
747     first.program = appData.firstChessProgram;
748     second.program = appData.secondChessProgram;
749     first.host = appData.firstHost;
750     second.host = appData.secondHost;
751     first.dir = appData.firstDirectory;
752     second.dir = appData.secondDirectory;
753     first.other = &second;
754     second.other = &first;
755     first.initString = appData.initString;
756     second.initString = appData.secondInitString;
757     first.computerString = appData.firstComputerString;
758     second.computerString = appData.secondComputerString;
759     first.useSigint = second.useSigint = TRUE;
760     first.useSigterm = second.useSigterm = TRUE;
761     first.reuse = appData.reuseFirst;
762     second.reuse = appData.reuseSecond;
763     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
764     second.nps = appData.secondNPS;
765     first.useSetboard = second.useSetboard = FALSE;
766     first.useSAN = second.useSAN = FALSE;
767     first.usePing = second.usePing = FALSE;
768     first.lastPing = second.lastPing = 0;
769     first.lastPong = second.lastPong = 0;
770     first.usePlayother = second.usePlayother = FALSE;
771     first.useColors = second.useColors = TRUE;
772     first.useUsermove = second.useUsermove = FALSE;
773     first.sendICS = second.sendICS = FALSE;
774     first.sendName = second.sendName = appData.icsActive;
775     first.sdKludge = second.sdKludge = FALSE;
776     first.stKludge = second.stKludge = FALSE;
777     TidyProgramName(first.program, first.host, first.tidy);
778     TidyProgramName(second.program, second.host, second.tidy);
779     first.matchWins = second.matchWins = 0;
780     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
781     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
782     first.analysisSupport = second.analysisSupport = 2; /* detect */
783     first.analyzing = second.analyzing = FALSE;
784     first.initDone = second.initDone = FALSE;
785
786     /* New features added by Tord: */
787     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
788     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     first.fenOverride  = appData.fenOverride1;
791     second.fenOverride = appData.fenOverride2;
792
793     /* [HGM] time odds: set factor for each machine */
794     first.timeOdds  = appData.firstTimeOdds;
795     second.timeOdds = appData.secondTimeOdds;
796     { float norm = 1;
797         if(appData.timeOddsMode) {
798             norm = first.timeOdds;
799             if(norm > second.timeOdds) norm = second.timeOdds;
800         }
801         first.timeOdds /= norm;
802         second.timeOdds /= norm;
803     }
804
805     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
806     first.accumulateTC = appData.firstAccumulateTC;
807     second.accumulateTC = appData.secondAccumulateTC;
808     first.maxNrOfSessions = second.maxNrOfSessions = 1;
809
810     /* [HGM] debug */
811     first.debug = second.debug = FALSE;
812     first.supportsNPS = second.supportsNPS = UNKNOWN;
813
814     /* [HGM] options */
815     first.optionSettings  = appData.firstOptions;
816     second.optionSettings = appData.secondOptions;
817
818     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
819     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
820     first.isUCI = appData.firstIsUCI; /* [AS] */
821     second.isUCI = appData.secondIsUCI; /* [AS] */
822     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
823     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
824
825     if (appData.firstProtocolVersion > PROTOVER
826         || appData.firstProtocolVersion < 1)
827       {
828         char buf[MSG_SIZ];
829         int len;
830
831         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
832                        appData.firstProtocolVersion);
833         if( (len > MSG_SIZ) && appData.debugMode )
834           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
835
836         DisplayFatalError(buf, 0, 2);
837       }
838     else
839       {
840         first.protocolVersion = appData.firstProtocolVersion;
841       }
842
843     if (appData.secondProtocolVersion > PROTOVER
844         || appData.secondProtocolVersion < 1)
845       {
846         char buf[MSG_SIZ];
847         int len;
848
849         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
850                        appData.secondProtocolVersion);
851         if( (len > MSG_SIZ) && appData.debugMode )
852           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
853
854         DisplayFatalError(buf, 0, 2);
855       }
856     else
857       {
858         second.protocolVersion = appData.secondProtocolVersion;
859       }
860
861     if (appData.icsActive) {
862         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
863 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
864     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
865         appData.clockMode = FALSE;
866         first.sendTime = second.sendTime = 0;
867     }
868
869 #if ZIPPY
870     /* Override some settings from environment variables, for backward
871        compatibility.  Unfortunately it's not feasible to have the env
872        vars just set defaults, at least in xboard.  Ugh.
873     */
874     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
875       ZippyInit();
876     }
877 #endif
878
879     if (appData.noChessProgram) {
880         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
881         sprintf(programVersion, "%s", PACKAGE_STRING);
882     } else {
883       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
884       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
885       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
886     }
887
888     if (!appData.icsActive) {
889       char buf[MSG_SIZ];
890       int len;
891
892       /* Check for variants that are supported only in ICS mode,
893          or not at all.  Some that are accepted here nevertheless
894          have bugs; see comments below.
895       */
896       VariantClass variant = StringToVariant(appData.variant);
897       switch (variant) {
898       case VariantBughouse:     /* need four players and two boards */
899       case VariantKriegspiel:   /* need to hide pieces and move details */
900         /* case VariantFischeRandom: (Fabien: moved below) */
901         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
902         if( (len > MSG_SIZ) && appData.debugMode )
903           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
904
905         DisplayFatalError(buf, 0, 2);
906         return;
907
908       case VariantUnknown:
909       case VariantLoadable:
910       case Variant29:
911       case Variant30:
912       case Variant31:
913       case Variant32:
914       case Variant33:
915       case Variant34:
916       case Variant35:
917       case Variant36:
918       default:
919         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
920         if( (len > MSG_SIZ) && appData.debugMode )
921           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
922
923         DisplayFatalError(buf, 0, 2);
924         return;
925
926       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
927       case VariantFairy:      /* [HGM] TestLegality definitely off! */
928       case VariantGothic:     /* [HGM] should work */
929       case VariantCapablanca: /* [HGM] should work */
930       case VariantCourier:    /* [HGM] initial forced moves not implemented */
931       case VariantShogi:      /* [HGM] could still mate with pawn drop */
932       case VariantKnightmate: /* [HGM] should work */
933       case VariantCylinder:   /* [HGM] untested */
934       case VariantFalcon:     /* [HGM] untested */
935       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
936                                  offboard interposition not understood */
937       case VariantNormal:     /* definitely works! */
938       case VariantWildCastle: /* pieces not automatically shuffled */
939       case VariantNoCastle:   /* pieces not automatically shuffled */
940       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
941       case VariantLosers:     /* should work except for win condition,
942                                  and doesn't know captures are mandatory */
943       case VariantSuicide:    /* should work except for win condition,
944                                  and doesn't know captures are mandatory */
945       case VariantGiveaway:   /* should work except for win condition,
946                                  and doesn't know captures are mandatory */
947       case VariantTwoKings:   /* should work */
948       case VariantAtomic:     /* should work except for win condition */
949       case Variant3Check:     /* should work except for win condition */
950       case VariantShatranj:   /* should work except for all win conditions */
951       case VariantMakruk:     /* should work except for daw countdown */
952       case VariantBerolina:   /* might work if TestLegality is off */
953       case VariantCapaRandom: /* should work */
954       case VariantJanus:      /* should work */
955       case VariantSuper:      /* experimental */
956       case VariantGreat:      /* experimental, requires legality testing to be off */
957       case VariantSChess:     /* S-Chess, should work */
958         break;
959       }
960     }
961
962     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
963     InitEngineUCI( installDir, &second );
964 }
965
966 int NextIntegerFromString( char ** str, long * value )
967 {
968     int result = -1;
969     char * s = *str;
970
971     while( *s == ' ' || *s == '\t' ) {
972         s++;
973     }
974
975     *value = 0;
976
977     if( *s >= '0' && *s <= '9' ) {
978         while( *s >= '0' && *s <= '9' ) {
979             *value = *value * 10 + (*s - '0');
980             s++;
981         }
982
983         result = 0;
984     }
985
986     *str = s;
987
988     return result;
989 }
990
991 int NextTimeControlFromString( char ** str, long * value )
992 {
993     long temp;
994     int result = NextIntegerFromString( str, &temp );
995
996     if( result == 0 ) {
997         *value = temp * 60; /* Minutes */
998         if( **str == ':' ) {
999             (*str)++;
1000             result = NextIntegerFromString( str, &temp );
1001             *value += temp; /* Seconds */
1002         }
1003     }
1004
1005     return result;
1006 }
1007
1008 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1009 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1010     int result = -1, type = 0; long temp, temp2;
1011
1012     if(**str != ':') return -1; // old params remain in force!
1013     (*str)++;
1014     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1015     if( NextIntegerFromString( str, &temp ) ) return -1;
1016     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1017
1018     if(**str != '/') {
1019         /* time only: incremental or sudden-death time control */
1020         if(**str == '+') { /* increment follows; read it */
1021             (*str)++;
1022             if(**str == '!') type = *(*str)++; // Bronstein TC
1023             if(result = NextIntegerFromString( str, &temp2)) return -1;
1024             *inc = temp2 * 1000;
1025             if(**str == '.') { // read fraction of increment
1026                 char *start = ++(*str);
1027                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1028                 temp2 *= 1000;
1029                 while(start++ < *str) temp2 /= 10;
1030                 *inc += temp2;
1031             }
1032         } else *inc = 0;
1033         *moves = 0; *tc = temp * 1000; *incType = type;
1034         return 0;
1035     }
1036
1037     (*str)++; /* classical time control */
1038     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1039
1040     if(result == 0) {
1041         *moves = temp;
1042         *tc    = temp2 * 1000;
1043         *inc   = 0;
1044         *incType = type;
1045     }
1046     return result;
1047 }
1048
1049 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1050 {   /* [HGM] get time to add from the multi-session time-control string */
1051     int incType, moves=1; /* kludge to force reading of first session */
1052     long time, increment;
1053     char *s = tcString;
1054
1055     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1056     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1057     do {
1058         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1059         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1060         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1061         if(movenr == -1) return time;    /* last move before new session     */
1062         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1063         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1064         if(!moves) return increment;     /* current session is incremental   */
1065         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1066     } while(movenr >= -1);               /* try again for next session       */
1067
1068     return 0; // no new time quota on this move
1069 }
1070
1071 int
1072 ParseTimeControl(tc, ti, mps)
1073      char *tc;
1074      float ti;
1075      int mps;
1076 {
1077   long tc1;
1078   long tc2;
1079   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1080   int min, sec=0;
1081
1082   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1083   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1084       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1085   if(ti > 0) {
1086
1087     if(mps)
1088       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1089     else 
1090       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1091   } else {
1092     if(mps)
1093       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1094     else 
1095       snprintf(buf, MSG_SIZ, ":%s", mytc);
1096   }
1097   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1098   
1099   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1100     return FALSE;
1101   }
1102
1103   if( *tc == '/' ) {
1104     /* Parse second time control */
1105     tc++;
1106
1107     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1108       return FALSE;
1109     }
1110
1111     if( tc2 == 0 ) {
1112       return FALSE;
1113     }
1114
1115     timeControl_2 = tc2 * 1000;
1116   }
1117   else {
1118     timeControl_2 = 0;
1119   }
1120
1121   if( tc1 == 0 ) {
1122     return FALSE;
1123   }
1124
1125   timeControl = tc1 * 1000;
1126
1127   if (ti >= 0) {
1128     timeIncrement = ti * 1000;  /* convert to ms */
1129     movesPerSession = 0;
1130   } else {
1131     timeIncrement = 0;
1132     movesPerSession = mps;
1133   }
1134   return TRUE;
1135 }
1136
1137 void
1138 InitBackEnd2()
1139 {
1140     if (appData.debugMode) {
1141         fprintf(debugFP, "%s\n", programVersion);
1142     }
1143
1144     set_cont_sequence(appData.wrapContSeq);
1145     if (appData.matchGames > 0) {
1146         appData.matchMode = TRUE;
1147     } else if (appData.matchMode) {
1148         appData.matchGames = 1;
1149     }
1150     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1151         appData.matchGames = appData.sameColorGames;
1152     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1153         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1154         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1155     }
1156     Reset(TRUE, FALSE);
1157     if (appData.noChessProgram || first.protocolVersion == 1) {
1158       InitBackEnd3();
1159     } else {
1160       /* kludge: allow timeout for initial "feature" commands */
1161       FreezeUI();
1162       DisplayMessage("", _("Starting chess program"));
1163       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1164     }
1165 }
1166
1167 void
1168 InitBackEnd3 P((void))
1169 {
1170     GameMode initialMode;
1171     char buf[MSG_SIZ];
1172     int err, len;
1173
1174     InitChessProgram(&first, startedFromSetupPosition);
1175
1176     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1177         free(programVersion);
1178         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1179         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1180     }
1181
1182     if (appData.icsActive) {
1183 #ifdef WIN32
1184         /* [DM] Make a console window if needed [HGM] merged ifs */
1185         ConsoleCreate();
1186 #endif
1187         err = establish();
1188         if (err != 0)
1189           {
1190             if (*appData.icsCommPort != NULLCHAR)
1191               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1192                              appData.icsCommPort);
1193             else
1194               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1195                         appData.icsHost, appData.icsPort);
1196
1197             if( (len > MSG_SIZ) && appData.debugMode )
1198               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1199
1200             DisplayFatalError(buf, err, 1);
1201             return;
1202         }
1203         SetICSMode();
1204         telnetISR =
1205           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1206         fromUserISR =
1207           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1208         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1209             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1210     } else if (appData.noChessProgram) {
1211         SetNCPMode();
1212     } else {
1213         SetGNUMode();
1214     }
1215
1216     if (*appData.cmailGameName != NULLCHAR) {
1217         SetCmailMode();
1218         OpenLoopback(&cmailPR);
1219         cmailISR =
1220           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1221     }
1222
1223     ThawUI();
1224     DisplayMessage("", "");
1225     if (StrCaseCmp(appData.initialMode, "") == 0) {
1226       initialMode = BeginningOfGame;
1227     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1228       initialMode = TwoMachinesPlay;
1229     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1230       initialMode = AnalyzeFile;
1231     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1232       initialMode = AnalyzeMode;
1233     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1234       initialMode = MachinePlaysWhite;
1235     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1236       initialMode = MachinePlaysBlack;
1237     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1238       initialMode = EditGame;
1239     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1240       initialMode = EditPosition;
1241     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1242       initialMode = Training;
1243     } else {
1244       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1245       if( (len > MSG_SIZ) && appData.debugMode )
1246         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1247
1248       DisplayFatalError(buf, 0, 2);
1249       return;
1250     }
1251
1252     if (appData.matchMode) {
1253         /* Set up machine vs. machine match */
1254         if (appData.noChessProgram) {
1255             DisplayFatalError(_("Can't have a match with no chess programs"),
1256                               0, 2);
1257             return;
1258         }
1259         matchMode = TRUE;
1260         matchGame = 1;
1261         if (*appData.loadGameFile != NULLCHAR) {
1262             int index = appData.loadGameIndex; // [HGM] autoinc
1263             if(index<0) lastIndex = index = 1;
1264             if (!LoadGameFromFile(appData.loadGameFile,
1265                                   index,
1266                                   appData.loadGameFile, FALSE)) {
1267                 DisplayFatalError(_("Bad game file"), 0, 1);
1268                 return;
1269             }
1270         } else if (*appData.loadPositionFile != NULLCHAR) {
1271             int index = appData.loadPositionIndex; // [HGM] autoinc
1272             if(index<0) lastIndex = index = 1;
1273             if (!LoadPositionFromFile(appData.loadPositionFile,
1274                                       index,
1275                                       appData.loadPositionFile)) {
1276                 DisplayFatalError(_("Bad position file"), 0, 1);
1277                 return;
1278             }
1279         }
1280         TwoMachinesEvent();
1281     } else if (*appData.cmailGameName != NULLCHAR) {
1282         /* Set up cmail mode */
1283         ReloadCmailMsgEvent(TRUE);
1284     } else {
1285         /* Set up other modes */
1286         if (initialMode == AnalyzeFile) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1289             return;
1290           }
1291         }
1292         if (*appData.loadGameFile != NULLCHAR) {
1293             (void) LoadGameFromFile(appData.loadGameFile,
1294                                     appData.loadGameIndex,
1295                                     appData.loadGameFile, TRUE);
1296         } else if (*appData.loadPositionFile != NULLCHAR) {
1297             (void) LoadPositionFromFile(appData.loadPositionFile,
1298                                         appData.loadPositionIndex,
1299                                         appData.loadPositionFile);
1300             /* [HGM] try to make self-starting even after FEN load */
1301             /* to allow automatic setup of fairy variants with wtm */
1302             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1303                 gameMode = BeginningOfGame;
1304                 setboardSpoiledMachineBlack = 1;
1305             }
1306             /* [HGM] loadPos: make that every new game uses the setup */
1307             /* from file as long as we do not switch variant          */
1308             if(!blackPlaysFirst) {
1309                 startedFromPositionFile = TRUE;
1310                 CopyBoard(filePosition, boards[0]);
1311             }
1312         }
1313         if (initialMode == AnalyzeMode) {
1314           if (appData.noChessProgram) {
1315             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1316             return;
1317           }
1318           if (appData.icsActive) {
1319             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1320             return;
1321           }
1322           AnalyzeModeEvent();
1323         } else if (initialMode == AnalyzeFile) {
1324           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1325           ShowThinkingEvent();
1326           AnalyzeFileEvent();
1327           AnalysisPeriodicEvent(1);
1328         } else if (initialMode == MachinePlaysWhite) {
1329           if (appData.noChessProgram) {
1330             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1331                               0, 2);
1332             return;
1333           }
1334           if (appData.icsActive) {
1335             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1336                               0, 2);
1337             return;
1338           }
1339           MachineWhiteEvent();
1340         } else if (initialMode == MachinePlaysBlack) {
1341           if (appData.noChessProgram) {
1342             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1343                               0, 2);
1344             return;
1345           }
1346           if (appData.icsActive) {
1347             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1348                               0, 2);
1349             return;
1350           }
1351           MachineBlackEvent();
1352         } else if (initialMode == TwoMachinesPlay) {
1353           if (appData.noChessProgram) {
1354             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1355                               0, 2);
1356             return;
1357           }
1358           if (appData.icsActive) {
1359             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1360                               0, 2);
1361             return;
1362           }
1363           TwoMachinesEvent();
1364         } else if (initialMode == EditGame) {
1365           EditGameEvent();
1366         } else if (initialMode == EditPosition) {
1367           EditPositionEvent();
1368         } else if (initialMode == Training) {
1369           if (*appData.loadGameFile == NULLCHAR) {
1370             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1371             return;
1372           }
1373           TrainingEvent();
1374         }
1375     }
1376 }
1377
1378 /*
1379  * Establish will establish a contact to a remote host.port.
1380  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1381  *  used to talk to the host.
1382  * Returns 0 if okay, error code if not.
1383  */
1384 int
1385 establish()
1386 {
1387     char buf[MSG_SIZ];
1388
1389     if (*appData.icsCommPort != NULLCHAR) {
1390         /* Talk to the host through a serial comm port */
1391         return OpenCommPort(appData.icsCommPort, &icsPR);
1392
1393     } else if (*appData.gateway != NULLCHAR) {
1394         if (*appData.remoteShell == NULLCHAR) {
1395             /* Use the rcmd protocol to run telnet program on a gateway host */
1396             snprintf(buf, sizeof(buf), "%s %s %s",
1397                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1398             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1399
1400         } else {
1401             /* Use the rsh program to run telnet program on a gateway host */
1402             if (*appData.remoteUser == NULLCHAR) {
1403                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1404                         appData.gateway, appData.telnetProgram,
1405                         appData.icsHost, appData.icsPort);
1406             } else {
1407                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1408                         appData.remoteShell, appData.gateway,
1409                         appData.remoteUser, appData.telnetProgram,
1410                         appData.icsHost, appData.icsPort);
1411             }
1412             return StartChildProcess(buf, "", &icsPR);
1413
1414         }
1415     } else if (appData.useTelnet) {
1416         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1417
1418     } else {
1419         /* TCP socket interface differs somewhat between
1420            Unix and NT; handle details in the front end.
1421            */
1422         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1423     }
1424 }
1425
1426 void EscapeExpand(char *p, char *q)
1427 {       // [HGM] initstring: routine to shape up string arguments
1428         while(*p++ = *q++) if(p[-1] == '\\')
1429             switch(*q++) {
1430                 case 'n': p[-1] = '\n'; break;
1431                 case 'r': p[-1] = '\r'; break;
1432                 case 't': p[-1] = '\t'; break;
1433                 case '\\': p[-1] = '\\'; break;
1434                 case 0: *p = 0; return;
1435                 default: p[-1] = q[-1]; break;
1436             }
1437 }
1438
1439 void
1440 show_bytes(fp, buf, count)
1441      FILE *fp;
1442      char *buf;
1443      int count;
1444 {
1445     while (count--) {
1446         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1447             fprintf(fp, "\\%03o", *buf & 0xff);
1448         } else {
1449             putc(*buf, fp);
1450         }
1451         buf++;
1452     }
1453     fflush(fp);
1454 }
1455
1456 /* Returns an errno value */
1457 int
1458 OutputMaybeTelnet(pr, message, count, outError)
1459      ProcRef pr;
1460      char *message;
1461      int count;
1462      int *outError;
1463 {
1464     char buf[8192], *p, *q, *buflim;
1465     int left, newcount, outcount;
1466
1467     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1468         *appData.gateway != NULLCHAR) {
1469         if (appData.debugMode) {
1470             fprintf(debugFP, ">ICS: ");
1471             show_bytes(debugFP, message, count);
1472             fprintf(debugFP, "\n");
1473         }
1474         return OutputToProcess(pr, message, count, outError);
1475     }
1476
1477     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1478     p = message;
1479     q = buf;
1480     left = count;
1481     newcount = 0;
1482     while (left) {
1483         if (q >= buflim) {
1484             if (appData.debugMode) {
1485                 fprintf(debugFP, ">ICS: ");
1486                 show_bytes(debugFP, buf, newcount);
1487                 fprintf(debugFP, "\n");
1488             }
1489             outcount = OutputToProcess(pr, buf, newcount, outError);
1490             if (outcount < newcount) return -1; /* to be sure */
1491             q = buf;
1492             newcount = 0;
1493         }
1494         if (*p == '\n') {
1495             *q++ = '\r';
1496             newcount++;
1497         } else if (((unsigned char) *p) == TN_IAC) {
1498             *q++ = (char) TN_IAC;
1499             newcount ++;
1500         }
1501         *q++ = *p++;
1502         newcount++;
1503         left--;
1504     }
1505     if (appData.debugMode) {
1506         fprintf(debugFP, ">ICS: ");
1507         show_bytes(debugFP, buf, newcount);
1508         fprintf(debugFP, "\n");
1509     }
1510     outcount = OutputToProcess(pr, buf, newcount, outError);
1511     if (outcount < newcount) return -1; /* to be sure */
1512     return count;
1513 }
1514
1515 void
1516 read_from_player(isr, closure, message, count, error)
1517      InputSourceRef isr;
1518      VOIDSTAR closure;
1519      char *message;
1520      int count;
1521      int error;
1522 {
1523     int outError, outCount;
1524     static int gotEof = 0;
1525
1526     /* Pass data read from player on to ICS */
1527     if (count > 0) {
1528         gotEof = 0;
1529         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1530         if (outCount < count) {
1531             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1532         }
1533     } else if (count < 0) {
1534         RemoveInputSource(isr);
1535         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1536     } else if (gotEof++ > 0) {
1537         RemoveInputSource(isr);
1538         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1539     }
1540 }
1541
1542 void
1543 KeepAlive()
1544 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1545     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1546     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1547     SendToICS("date\n");
1548     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1549 }
1550
1551 /* added routine for printf style output to ics */
1552 void ics_printf(char *format, ...)
1553 {
1554     char buffer[MSG_SIZ];
1555     va_list args;
1556
1557     va_start(args, format);
1558     vsnprintf(buffer, sizeof(buffer), format, args);
1559     buffer[sizeof(buffer)-1] = '\0';
1560     SendToICS(buffer);
1561     va_end(args);
1562 }
1563
1564 void
1565 SendToICS(s)
1566      char *s;
1567 {
1568     int count, outCount, outError;
1569
1570     if (icsPR == NULL) return;
1571
1572     count = strlen(s);
1573     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1574     if (outCount < count) {
1575         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1576     }
1577 }
1578
1579 /* This is used for sending logon scripts to the ICS. Sending
1580    without a delay causes problems when using timestamp on ICC
1581    (at least on my machine). */
1582 void
1583 SendToICSDelayed(s,msdelay)
1584      char *s;
1585      long msdelay;
1586 {
1587     int count, outCount, outError;
1588
1589     if (icsPR == NULL) return;
1590
1591     count = strlen(s);
1592     if (appData.debugMode) {
1593         fprintf(debugFP, ">ICS: ");
1594         show_bytes(debugFP, s, count);
1595         fprintf(debugFP, "\n");
1596     }
1597     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1598                                       msdelay);
1599     if (outCount < count) {
1600         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1601     }
1602 }
1603
1604
1605 /* Remove all highlighting escape sequences in s
1606    Also deletes any suffix starting with '('
1607    */
1608 char *
1609 StripHighlightAndTitle(s)
1610      char *s;
1611 {
1612     static char retbuf[MSG_SIZ];
1613     char *p = retbuf;
1614
1615     while (*s != NULLCHAR) {
1616         while (*s == '\033') {
1617             while (*s != NULLCHAR && !isalpha(*s)) s++;
1618             if (*s != NULLCHAR) s++;
1619         }
1620         while (*s != NULLCHAR && *s != '\033') {
1621             if (*s == '(' || *s == '[') {
1622                 *p = NULLCHAR;
1623                 return retbuf;
1624             }
1625             *p++ = *s++;
1626         }
1627     }
1628     *p = NULLCHAR;
1629     return retbuf;
1630 }
1631
1632 /* Remove all highlighting escape sequences in s */
1633 char *
1634 StripHighlight(s)
1635      char *s;
1636 {
1637     static char retbuf[MSG_SIZ];
1638     char *p = retbuf;
1639
1640     while (*s != NULLCHAR) {
1641         while (*s == '\033') {
1642             while (*s != NULLCHAR && !isalpha(*s)) s++;
1643             if (*s != NULLCHAR) s++;
1644         }
1645         while (*s != NULLCHAR && *s != '\033') {
1646             *p++ = *s++;
1647         }
1648     }
1649     *p = NULLCHAR;
1650     return retbuf;
1651 }
1652
1653 char *variantNames[] = VARIANT_NAMES;
1654 char *
1655 VariantName(v)
1656      VariantClass v;
1657 {
1658     return variantNames[v];
1659 }
1660
1661
1662 /* Identify a variant from the strings the chess servers use or the
1663    PGN Variant tag names we use. */
1664 VariantClass
1665 StringToVariant(e)
1666      char *e;
1667 {
1668     char *p;
1669     int wnum = -1;
1670     VariantClass v = VariantNormal;
1671     int i, found = FALSE;
1672     char buf[MSG_SIZ];
1673     int len;
1674
1675     if (!e) return v;
1676
1677     /* [HGM] skip over optional board-size prefixes */
1678     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1679         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1680         while( *e++ != '_');
1681     }
1682
1683     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1684         v = VariantNormal;
1685         found = TRUE;
1686     } else
1687     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1688       if (StrCaseStr(e, variantNames[i])) {
1689         v = (VariantClass) i;
1690         found = TRUE;
1691         break;
1692       }
1693     }
1694
1695     if (!found) {
1696       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1697           || StrCaseStr(e, "wild/fr")
1698           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1699         v = VariantFischeRandom;
1700       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1701                  (i = 1, p = StrCaseStr(e, "w"))) {
1702         p += i;
1703         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1704         if (isdigit(*p)) {
1705           wnum = atoi(p);
1706         } else {
1707           wnum = -1;
1708         }
1709         switch (wnum) {
1710         case 0: /* FICS only, actually */
1711         case 1:
1712           /* Castling legal even if K starts on d-file */
1713           v = VariantWildCastle;
1714           break;
1715         case 2:
1716         case 3:
1717         case 4:
1718           /* Castling illegal even if K & R happen to start in
1719              normal positions. */
1720           v = VariantNoCastle;
1721           break;
1722         case 5:
1723         case 7:
1724         case 8:
1725         case 10:
1726         case 11:
1727         case 12:
1728         case 13:
1729         case 14:
1730         case 15:
1731         case 18:
1732         case 19:
1733           /* Castling legal iff K & R start in normal positions */
1734           v = VariantNormal;
1735           break;
1736         case 6:
1737         case 20:
1738         case 21:
1739           /* Special wilds for position setup; unclear what to do here */
1740           v = VariantLoadable;
1741           break;
1742         case 9:
1743           /* Bizarre ICC game */
1744           v = VariantTwoKings;
1745           break;
1746         case 16:
1747           v = VariantKriegspiel;
1748           break;
1749         case 17:
1750           v = VariantLosers;
1751           break;
1752         case 22:
1753           v = VariantFischeRandom;
1754           break;
1755         case 23:
1756           v = VariantCrazyhouse;
1757           break;
1758         case 24:
1759           v = VariantBughouse;
1760           break;
1761         case 25:
1762           v = Variant3Check;
1763           break;
1764         case 26:
1765           /* Not quite the same as FICS suicide! */
1766           v = VariantGiveaway;
1767           break;
1768         case 27:
1769           v = VariantAtomic;
1770           break;
1771         case 28:
1772           v = VariantShatranj;
1773           break;
1774
1775         /* Temporary names for future ICC types.  The name *will* change in
1776            the next xboard/WinBoard release after ICC defines it. */
1777         case 29:
1778           v = Variant29;
1779           break;
1780         case 30:
1781           v = Variant30;
1782           break;
1783         case 31:
1784           v = Variant31;
1785           break;
1786         case 32:
1787           v = Variant32;
1788           break;
1789         case 33:
1790           v = Variant33;
1791           break;
1792         case 34:
1793           v = Variant34;
1794           break;
1795         case 35:
1796           v = Variant35;
1797           break;
1798         case 36:
1799           v = Variant36;
1800           break;
1801         case 37:
1802           v = VariantShogi;
1803           break;
1804         case 38:
1805           v = VariantXiangqi;
1806           break;
1807         case 39:
1808           v = VariantCourier;
1809           break;
1810         case 40:
1811           v = VariantGothic;
1812           break;
1813         case 41:
1814           v = VariantCapablanca;
1815           break;
1816         case 42:
1817           v = VariantKnightmate;
1818           break;
1819         case 43:
1820           v = VariantFairy;
1821           break;
1822         case 44:
1823           v = VariantCylinder;
1824           break;
1825         case 45:
1826           v = VariantFalcon;
1827           break;
1828         case 46:
1829           v = VariantCapaRandom;
1830           break;
1831         case 47:
1832           v = VariantBerolina;
1833           break;
1834         case 48:
1835           v = VariantJanus;
1836           break;
1837         case 49:
1838           v = VariantSuper;
1839           break;
1840         case 50:
1841           v = VariantGreat;
1842           break;
1843         case -1:
1844           /* Found "wild" or "w" in the string but no number;
1845              must assume it's normal chess. */
1846           v = VariantNormal;
1847           break;
1848         default:
1849           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1850           if( (len > MSG_SIZ) && appData.debugMode )
1851             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1852
1853           DisplayError(buf, 0);
1854           v = VariantUnknown;
1855           break;
1856         }
1857       }
1858     }
1859     if (appData.debugMode) {
1860       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1861               e, wnum, VariantName(v));
1862     }
1863     return v;
1864 }
1865
1866 static int leftover_start = 0, leftover_len = 0;
1867 char star_match[STAR_MATCH_N][MSG_SIZ];
1868
1869 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1870    advance *index beyond it, and set leftover_start to the new value of
1871    *index; else return FALSE.  If pattern contains the character '*', it
1872    matches any sequence of characters not containing '\r', '\n', or the
1873    character following the '*' (if any), and the matched sequence(s) are
1874    copied into star_match.
1875    */
1876 int
1877 looking_at(buf, index, pattern)
1878      char *buf;
1879      int *index;
1880      char *pattern;
1881 {
1882     char *bufp = &buf[*index], *patternp = pattern;
1883     int star_count = 0;
1884     char *matchp = star_match[0];
1885
1886     for (;;) {
1887         if (*patternp == NULLCHAR) {
1888             *index = leftover_start = bufp - buf;
1889             *matchp = NULLCHAR;
1890             return TRUE;
1891         }
1892         if (*bufp == NULLCHAR) return FALSE;
1893         if (*patternp == '*') {
1894             if (*bufp == *(patternp + 1)) {
1895                 *matchp = NULLCHAR;
1896                 matchp = star_match[++star_count];
1897                 patternp += 2;
1898                 bufp++;
1899                 continue;
1900             } else if (*bufp == '\n' || *bufp == '\r') {
1901                 patternp++;
1902                 if (*patternp == NULLCHAR)
1903                   continue;
1904                 else
1905                   return FALSE;
1906             } else {
1907                 *matchp++ = *bufp++;
1908                 continue;
1909             }
1910         }
1911         if (*patternp != *bufp) return FALSE;
1912         patternp++;
1913         bufp++;
1914     }
1915 }
1916
1917 void
1918 SendToPlayer(data, length)
1919      char *data;
1920      int length;
1921 {
1922     int error, outCount;
1923     outCount = OutputToProcess(NoProc, data, length, &error);
1924     if (outCount < length) {
1925         DisplayFatalError(_("Error writing to display"), error, 1);
1926     }
1927 }
1928
1929 void
1930 PackHolding(packed, holding)
1931      char packed[];
1932      char *holding;
1933 {
1934     char *p = holding;
1935     char *q = packed;
1936     int runlength = 0;
1937     int curr = 9999;
1938     do {
1939         if (*p == curr) {
1940             runlength++;
1941         } else {
1942             switch (runlength) {
1943               case 0:
1944                 break;
1945               case 1:
1946                 *q++ = curr;
1947                 break;
1948               case 2:
1949                 *q++ = curr;
1950                 *q++ = curr;
1951                 break;
1952               default:
1953                 sprintf(q, "%d", runlength);
1954                 while (*q) q++;
1955                 *q++ = curr;
1956                 break;
1957             }
1958             runlength = 1;
1959             curr = *p;
1960         }
1961     } while (*p++);
1962     *q = NULLCHAR;
1963 }
1964
1965 /* Telnet protocol requests from the front end */
1966 void
1967 TelnetRequest(ddww, option)
1968      unsigned char ddww, option;
1969 {
1970     unsigned char msg[3];
1971     int outCount, outError;
1972
1973     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1974
1975     if (appData.debugMode) {
1976         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1977         switch (ddww) {
1978           case TN_DO:
1979             ddwwStr = "DO";
1980             break;
1981           case TN_DONT:
1982             ddwwStr = "DONT";
1983             break;
1984           case TN_WILL:
1985             ddwwStr = "WILL";
1986             break;
1987           case TN_WONT:
1988             ddwwStr = "WONT";
1989             break;
1990           default:
1991             ddwwStr = buf1;
1992             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1993             break;
1994         }
1995         switch (option) {
1996           case TN_ECHO:
1997             optionStr = "ECHO";
1998             break;
1999           default:
2000             optionStr = buf2;
2001             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2002             break;
2003         }
2004         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2005     }
2006     msg[0] = TN_IAC;
2007     msg[1] = ddww;
2008     msg[2] = option;
2009     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2010     if (outCount < 3) {
2011         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2012     }
2013 }
2014
2015 void
2016 DoEcho()
2017 {
2018     if (!appData.icsActive) return;
2019     TelnetRequest(TN_DO, TN_ECHO);
2020 }
2021
2022 void
2023 DontEcho()
2024 {
2025     if (!appData.icsActive) return;
2026     TelnetRequest(TN_DONT, TN_ECHO);
2027 }
2028
2029 void
2030 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2031 {
2032     /* put the holdings sent to us by the server on the board holdings area */
2033     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2034     char p;
2035     ChessSquare piece;
2036
2037     if(gameInfo.holdingsWidth < 2)  return;
2038     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2039         return; // prevent overwriting by pre-board holdings
2040
2041     if( (int)lowestPiece >= BlackPawn ) {
2042         holdingsColumn = 0;
2043         countsColumn = 1;
2044         holdingsStartRow = BOARD_HEIGHT-1;
2045         direction = -1;
2046     } else {
2047         holdingsColumn = BOARD_WIDTH-1;
2048         countsColumn = BOARD_WIDTH-2;
2049         holdingsStartRow = 0;
2050         direction = 1;
2051     }
2052
2053     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2054         board[i][holdingsColumn] = EmptySquare;
2055         board[i][countsColumn]   = (ChessSquare) 0;
2056     }
2057     while( (p=*holdings++) != NULLCHAR ) {
2058         piece = CharToPiece( ToUpper(p) );
2059         if(piece == EmptySquare) continue;
2060         /*j = (int) piece - (int) WhitePawn;*/
2061         j = PieceToNumber(piece);
2062         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2063         if(j < 0) continue;               /* should not happen */
2064         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2065         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2066         board[holdingsStartRow+j*direction][countsColumn]++;
2067     }
2068 }
2069
2070
2071 void
2072 VariantSwitch(Board board, VariantClass newVariant)
2073 {
2074    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2075    static Board oldBoard;
2076
2077    startedFromPositionFile = FALSE;
2078    if(gameInfo.variant == newVariant) return;
2079
2080    /* [HGM] This routine is called each time an assignment is made to
2081     * gameInfo.variant during a game, to make sure the board sizes
2082     * are set to match the new variant. If that means adding or deleting
2083     * holdings, we shift the playing board accordingly
2084     * This kludge is needed because in ICS observe mode, we get boards
2085     * of an ongoing game without knowing the variant, and learn about the
2086     * latter only later. This can be because of the move list we requested,
2087     * in which case the game history is refilled from the beginning anyway,
2088     * but also when receiving holdings of a crazyhouse game. In the latter
2089     * case we want to add those holdings to the already received position.
2090     */
2091
2092
2093    if (appData.debugMode) {
2094      fprintf(debugFP, "Switch board from %s to %s\n",
2095              VariantName(gameInfo.variant), VariantName(newVariant));
2096      setbuf(debugFP, NULL);
2097    }
2098    shuffleOpenings = 0;       /* [HGM] shuffle */
2099    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2100    switch(newVariant)
2101      {
2102      case VariantShogi:
2103        newWidth = 9;  newHeight = 9;
2104        gameInfo.holdingsSize = 7;
2105      case VariantBughouse:
2106      case VariantCrazyhouse:
2107        newHoldingsWidth = 2; break;
2108      case VariantGreat:
2109        newWidth = 10;
2110      case VariantSuper:
2111        newHoldingsWidth = 2;
2112        gameInfo.holdingsSize = 8;
2113        break;
2114      case VariantGothic:
2115      case VariantCapablanca:
2116      case VariantCapaRandom:
2117        newWidth = 10;
2118      default:
2119        newHoldingsWidth = gameInfo.holdingsSize = 0;
2120      };
2121
2122    if(newWidth  != gameInfo.boardWidth  ||
2123       newHeight != gameInfo.boardHeight ||
2124       newHoldingsWidth != gameInfo.holdingsWidth ) {
2125
2126      /* shift position to new playing area, if needed */
2127      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2128        for(i=0; i<BOARD_HEIGHT; i++)
2129          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2130            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2131              board[i][j];
2132        for(i=0; i<newHeight; i++) {
2133          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2134          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2135        }
2136      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2137        for(i=0; i<BOARD_HEIGHT; i++)
2138          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2139            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2140              board[i][j];
2141      }
2142      gameInfo.boardWidth  = newWidth;
2143      gameInfo.boardHeight = newHeight;
2144      gameInfo.holdingsWidth = newHoldingsWidth;
2145      gameInfo.variant = newVariant;
2146      InitDrawingSizes(-2, 0);
2147    } else gameInfo.variant = newVariant;
2148    CopyBoard(oldBoard, board);   // remember correctly formatted board
2149      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2150    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2151 }
2152
2153 static int loggedOn = FALSE;
2154
2155 /*-- Game start info cache: --*/
2156 int gs_gamenum;
2157 char gs_kind[MSG_SIZ];
2158 static char player1Name[128] = "";
2159 static char player2Name[128] = "";
2160 static char cont_seq[] = "\n\\   ";
2161 static int player1Rating = -1;
2162 static int player2Rating = -1;
2163 /*----------------------------*/
2164
2165 ColorClass curColor = ColorNormal;
2166 int suppressKibitz = 0;
2167
2168 // [HGM] seekgraph
2169 Boolean soughtPending = FALSE;
2170 Boolean seekGraphUp;
2171 #define MAX_SEEK_ADS 200
2172 #define SQUARE 0x80
2173 char *seekAdList[MAX_SEEK_ADS];
2174 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2175 float tcList[MAX_SEEK_ADS];
2176 char colorList[MAX_SEEK_ADS];
2177 int nrOfSeekAds = 0;
2178 int minRating = 1010, maxRating = 2800;
2179 int hMargin = 10, vMargin = 20, h, w;
2180 extern int squareSize, lineGap;
2181
2182 void
2183 PlotSeekAd(int i)
2184 {
2185         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2186         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2187         if(r < minRating+100 && r >=0 ) r = minRating+100;
2188         if(r > maxRating) r = maxRating;
2189         if(tc < 1.) tc = 1.;
2190         if(tc > 95.) tc = 95.;
2191         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2192         y = ((double)r - minRating)/(maxRating - minRating)
2193             * (h-vMargin-squareSize/8-1) + vMargin;
2194         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2195         if(strstr(seekAdList[i], " u ")) color = 1;
2196         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2197            !strstr(seekAdList[i], "bullet") &&
2198            !strstr(seekAdList[i], "blitz") &&
2199            !strstr(seekAdList[i], "standard") ) color = 2;
2200         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2201         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2202 }
2203
2204 void
2205 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2206 {
2207         char buf[MSG_SIZ], *ext = "";
2208         VariantClass v = StringToVariant(type);
2209         if(strstr(type, "wild")) {
2210             ext = type + 4; // append wild number
2211             if(v == VariantFischeRandom) type = "chess960"; else
2212             if(v == VariantLoadable) type = "setup"; else
2213             type = VariantName(v);
2214         }
2215         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2216         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2217             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2218             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2219             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2220             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2221             seekNrList[nrOfSeekAds] = nr;
2222             zList[nrOfSeekAds] = 0;
2223             seekAdList[nrOfSeekAds++] = StrSave(buf);
2224             if(plot) PlotSeekAd(nrOfSeekAds-1);
2225         }
2226 }
2227
2228 void
2229 EraseSeekDot(int i)
2230 {
2231     int x = xList[i], y = yList[i], d=squareSize/4, k;
2232     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2233     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2234     // now replot every dot that overlapped
2235     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2236         int xx = xList[k], yy = yList[k];
2237         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2238             DrawSeekDot(xx, yy, colorList[k]);
2239     }
2240 }
2241
2242 void
2243 RemoveSeekAd(int nr)
2244 {
2245         int i;
2246         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2247             EraseSeekDot(i);
2248             if(seekAdList[i]) free(seekAdList[i]);
2249             seekAdList[i] = seekAdList[--nrOfSeekAds];
2250             seekNrList[i] = seekNrList[nrOfSeekAds];
2251             ratingList[i] = ratingList[nrOfSeekAds];
2252             colorList[i]  = colorList[nrOfSeekAds];
2253             tcList[i] = tcList[nrOfSeekAds];
2254             xList[i]  = xList[nrOfSeekAds];
2255             yList[i]  = yList[nrOfSeekAds];
2256             zList[i]  = zList[nrOfSeekAds];
2257             seekAdList[nrOfSeekAds] = NULL;
2258             break;
2259         }
2260 }
2261
2262 Boolean
2263 MatchSoughtLine(char *line)
2264 {
2265     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2266     int nr, base, inc, u=0; char dummy;
2267
2268     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2269        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2270        (u=1) &&
2271        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2273         // match: compact and save the line
2274         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2275         return TRUE;
2276     }
2277     return FALSE;
2278 }
2279
2280 int
2281 DrawSeekGraph()
2282 {
2283     int i;
2284     if(!seekGraphUp) return FALSE;
2285     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2286     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2287
2288     DrawSeekBackground(0, 0, w, h);
2289     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2290     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2291     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2292         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2293         yy = h-1-yy;
2294         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2295         if(i%500 == 0) {
2296             char buf[MSG_SIZ];
2297             snprintf(buf, MSG_SIZ, "%d", i);
2298             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2299         }
2300     }
2301     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2302     for(i=1; i<100; i+=(i<10?1:5)) {
2303         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2304         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2305         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2306             char buf[MSG_SIZ];
2307             snprintf(buf, MSG_SIZ, "%d", i);
2308             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2309         }
2310     }
2311     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2312     return TRUE;
2313 }
2314
2315 int SeekGraphClick(ClickType click, int x, int y, int moving)
2316 {
2317     static int lastDown = 0, displayed = 0, lastSecond;
2318     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2319         if(click == Release || moving) return FALSE;
2320         nrOfSeekAds = 0;
2321         soughtPending = TRUE;
2322         SendToICS(ics_prefix);
2323         SendToICS("sought\n"); // should this be "sought all"?
2324     } else { // issue challenge based on clicked ad
2325         int dist = 10000; int i, closest = 0, second = 0;
2326         for(i=0; i<nrOfSeekAds; i++) {
2327             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2328             if(d < dist) { dist = d; closest = i; }
2329             second += (d - zList[i] < 120); // count in-range ads
2330             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2331         }
2332         if(dist < 120) {
2333             char buf[MSG_SIZ];
2334             second = (second > 1);
2335             if(displayed != closest || second != lastSecond) {
2336                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2337                 lastSecond = second; displayed = closest;
2338             }
2339             if(click == Press) {
2340                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2341                 lastDown = closest;
2342                 return TRUE;
2343             } // on press 'hit', only show info
2344             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2345             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2346             SendToICS(ics_prefix);
2347             SendToICS(buf);
2348             return TRUE; // let incoming board of started game pop down the graph
2349         } else if(click == Release) { // release 'miss' is ignored
2350             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2351             if(moving == 2) { // right up-click
2352                 nrOfSeekAds = 0; // refresh graph
2353                 soughtPending = TRUE;
2354                 SendToICS(ics_prefix);
2355                 SendToICS("sought\n"); // should this be "sought all"?
2356             }
2357             return TRUE;
2358         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2359         // press miss or release hit 'pop down' seek graph
2360         seekGraphUp = FALSE;
2361         DrawPosition(TRUE, NULL);
2362     }
2363     return TRUE;
2364 }
2365
2366 void
2367 read_from_ics(isr, closure, data, count, error)
2368      InputSourceRef isr;
2369      VOIDSTAR closure;
2370      char *data;
2371      int count;
2372      int error;
2373 {
2374 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2375 #define STARTED_NONE 0
2376 #define STARTED_MOVES 1
2377 #define STARTED_BOARD 2
2378 #define STARTED_OBSERVE 3
2379 #define STARTED_HOLDINGS 4
2380 #define STARTED_CHATTER 5
2381 #define STARTED_COMMENT 6
2382 #define STARTED_MOVES_NOHIDE 7
2383
2384     static int started = STARTED_NONE;
2385     static char parse[20000];
2386     static int parse_pos = 0;
2387     static char buf[BUF_SIZE + 1];
2388     static int firstTime = TRUE, intfSet = FALSE;
2389     static ColorClass prevColor = ColorNormal;
2390     static int savingComment = FALSE;
2391     static int cmatch = 0; // continuation sequence match
2392     char *bp;
2393     char str[MSG_SIZ];
2394     int i, oldi;
2395     int buf_len;
2396     int next_out;
2397     int tkind;
2398     int backup;    /* [DM] For zippy color lines */
2399     char *p;
2400     char talker[MSG_SIZ]; // [HGM] chat
2401     int channel;
2402
2403     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2404
2405     if (appData.debugMode) {
2406       if (!error) {
2407         fprintf(debugFP, "<ICS: ");
2408         show_bytes(debugFP, data, count);
2409         fprintf(debugFP, "\n");
2410       }
2411     }
2412
2413     if (appData.debugMode) { int f = forwardMostMove;
2414         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2415                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2416                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2417     }
2418     if (count > 0) {
2419         /* If last read ended with a partial line that we couldn't parse,
2420            prepend it to the new read and try again. */
2421         if (leftover_len > 0) {
2422             for (i=0; i<leftover_len; i++)
2423               buf[i] = buf[leftover_start + i];
2424         }
2425
2426     /* copy new characters into the buffer */
2427     bp = buf + leftover_len;
2428     buf_len=leftover_len;
2429     for (i=0; i<count; i++)
2430     {
2431         // ignore these
2432         if (data[i] == '\r')
2433             continue;
2434
2435         // join lines split by ICS?
2436         if (!appData.noJoin)
2437         {
2438             /*
2439                 Joining just consists of finding matches against the
2440                 continuation sequence, and discarding that sequence
2441                 if found instead of copying it.  So, until a match
2442                 fails, there's nothing to do since it might be the
2443                 complete sequence, and thus, something we don't want
2444                 copied.
2445             */
2446             if (data[i] == cont_seq[cmatch])
2447             {
2448                 cmatch++;
2449                 if (cmatch == strlen(cont_seq))
2450                 {
2451                     cmatch = 0; // complete match.  just reset the counter
2452
2453                     /*
2454                         it's possible for the ICS to not include the space
2455                         at the end of the last word, making our [correct]
2456                         join operation fuse two separate words.  the server
2457                         does this when the space occurs at the width setting.
2458                     */
2459                     if (!buf_len || buf[buf_len-1] != ' ')
2460                     {
2461                         *bp++ = ' ';
2462                         buf_len++;
2463                     }
2464                 }
2465                 continue;
2466             }
2467             else if (cmatch)
2468             {
2469                 /*
2470                     match failed, so we have to copy what matched before
2471                     falling through and copying this character.  In reality,
2472                     this will only ever be just the newline character, but
2473                     it doesn't hurt to be precise.
2474                 */
2475                 strncpy(bp, cont_seq, cmatch);
2476                 bp += cmatch;
2477                 buf_len += cmatch;
2478                 cmatch = 0;
2479             }
2480         }
2481
2482         // copy this char
2483         *bp++ = data[i];
2484         buf_len++;
2485     }
2486
2487         buf[buf_len] = NULLCHAR;
2488 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2489         next_out = 0;
2490         leftover_start = 0;
2491
2492         i = 0;
2493         while (i < buf_len) {
2494             /* Deal with part of the TELNET option negotiation
2495                protocol.  We refuse to do anything beyond the
2496                defaults, except that we allow the WILL ECHO option,
2497                which ICS uses to turn off password echoing when we are
2498                directly connected to it.  We reject this option
2499                if localLineEditing mode is on (always on in xboard)
2500                and we are talking to port 23, which might be a real
2501                telnet server that will try to keep WILL ECHO on permanently.
2502              */
2503             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2504                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2505                 unsigned char option;
2506                 oldi = i;
2507                 switch ((unsigned char) buf[++i]) {
2508                   case TN_WILL:
2509                     if (appData.debugMode)
2510                       fprintf(debugFP, "\n<WILL ");
2511                     switch (option = (unsigned char) buf[++i]) {
2512                       case TN_ECHO:
2513                         if (appData.debugMode)
2514                           fprintf(debugFP, "ECHO ");
2515                         /* Reply only if this is a change, according
2516                            to the protocol rules. */
2517                         if (remoteEchoOption) break;
2518                         if (appData.localLineEditing &&
2519                             atoi(appData.icsPort) == TN_PORT) {
2520                             TelnetRequest(TN_DONT, TN_ECHO);
2521                         } else {
2522                             EchoOff();
2523                             TelnetRequest(TN_DO, TN_ECHO);
2524                             remoteEchoOption = TRUE;
2525                         }
2526                         break;
2527                       default:
2528                         if (appData.debugMode)
2529                           fprintf(debugFP, "%d ", option);
2530                         /* Whatever this is, we don't want it. */
2531                         TelnetRequest(TN_DONT, option);
2532                         break;
2533                     }
2534                     break;
2535                   case TN_WONT:
2536                     if (appData.debugMode)
2537                       fprintf(debugFP, "\n<WONT ");
2538                     switch (option = (unsigned char) buf[++i]) {
2539                       case TN_ECHO:
2540                         if (appData.debugMode)
2541                           fprintf(debugFP, "ECHO ");
2542                         /* Reply only if this is a change, according
2543                            to the protocol rules. */
2544                         if (!remoteEchoOption) break;
2545                         EchoOn();
2546                         TelnetRequest(TN_DONT, TN_ECHO);
2547                         remoteEchoOption = FALSE;
2548                         break;
2549                       default:
2550                         if (appData.debugMode)
2551                           fprintf(debugFP, "%d ", (unsigned char) option);
2552                         /* Whatever this is, it must already be turned
2553                            off, because we never agree to turn on
2554                            anything non-default, so according to the
2555                            protocol rules, we don't reply. */
2556                         break;
2557                     }
2558                     break;
2559                   case TN_DO:
2560                     if (appData.debugMode)
2561                       fprintf(debugFP, "\n<DO ");
2562                     switch (option = (unsigned char) buf[++i]) {
2563                       default:
2564                         /* Whatever this is, we refuse to do it. */
2565                         if (appData.debugMode)
2566                           fprintf(debugFP, "%d ", option);
2567                         TelnetRequest(TN_WONT, option);
2568                         break;
2569                     }
2570                     break;
2571                   case TN_DONT:
2572                     if (appData.debugMode)
2573                       fprintf(debugFP, "\n<DONT ");
2574                     switch (option = (unsigned char) buf[++i]) {
2575                       default:
2576                         if (appData.debugMode)
2577                           fprintf(debugFP, "%d ", option);
2578                         /* Whatever this is, we are already not doing
2579                            it, because we never agree to do anything
2580                            non-default, so according to the protocol
2581                            rules, we don't reply. */
2582                         break;
2583                     }
2584                     break;
2585                   case TN_IAC:
2586                     if (appData.debugMode)
2587                       fprintf(debugFP, "\n<IAC ");
2588                     /* Doubled IAC; pass it through */
2589                     i--;
2590                     break;
2591                   default:
2592                     if (appData.debugMode)
2593                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2594                     /* Drop all other telnet commands on the floor */
2595                     break;
2596                 }
2597                 if (oldi > next_out)
2598                   SendToPlayer(&buf[next_out], oldi - next_out);
2599                 if (++i > next_out)
2600                   next_out = i;
2601                 continue;
2602             }
2603
2604             /* OK, this at least will *usually* work */
2605             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2606                 loggedOn = TRUE;
2607             }
2608
2609             if (loggedOn && !intfSet) {
2610                 if (ics_type == ICS_ICC) {
2611                   snprintf(str, MSG_SIZ,
2612                           "/set-quietly interface %s\n/set-quietly style 12\n",
2613                           programVersion);
2614                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2615                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2616                 } else if (ics_type == ICS_CHESSNET) {
2617                   snprintf(str, MSG_SIZ, "/style 12\n");
2618                 } else {
2619                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2620                   strcat(str, programVersion);
2621                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2624 #ifdef WIN32
2625                   strcat(str, "$iset nohighlight 1\n");
2626 #endif
2627                   strcat(str, "$iset lock 1\n$style 12\n");
2628                 }
2629                 SendToICS(str);
2630                 NotifyFrontendLogin();
2631                 intfSet = TRUE;
2632             }
2633
2634             if (started == STARTED_COMMENT) {
2635                 /* Accumulate characters in comment */
2636                 parse[parse_pos++] = buf[i];
2637                 if (buf[i] == '\n') {
2638                     parse[parse_pos] = NULLCHAR;
2639                     if(chattingPartner>=0) {
2640                         char mess[MSG_SIZ];
2641                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2642                         OutputChatMessage(chattingPartner, mess);
2643                         chattingPartner = -1;
2644                         next_out = i+1; // [HGM] suppress printing in ICS window
2645                     } else
2646                     if(!suppressKibitz) // [HGM] kibitz
2647                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2648                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2649                         int nrDigit = 0, nrAlph = 0, j;
2650                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2651                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2652                         parse[parse_pos] = NULLCHAR;
2653                         // try to be smart: if it does not look like search info, it should go to
2654                         // ICS interaction window after all, not to engine-output window.
2655                         for(j=0; j<parse_pos; j++) { // count letters and digits
2656                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2657                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2658                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2659                         }
2660                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2661                             int depth=0; float score;
2662                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2663                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2664                                 pvInfoList[forwardMostMove-1].depth = depth;
2665                                 pvInfoList[forwardMostMove-1].score = 100*score;
2666                             }
2667                             OutputKibitz(suppressKibitz, parse);
2668                         } else {
2669                             char tmp[MSG_SIZ];
2670                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2671                             SendToPlayer(tmp, strlen(tmp));
2672                         }
2673                         next_out = i+1; // [HGM] suppress printing in ICS window
2674                     }
2675                     started = STARTED_NONE;
2676                 } else {
2677                     /* Don't match patterns against characters in comment */
2678                     i++;
2679                     continue;
2680                 }
2681             }
2682             if (started == STARTED_CHATTER) {
2683                 if (buf[i] != '\n') {
2684                     /* Don't match patterns against characters in chatter */
2685                     i++;
2686                     continue;
2687                 }
2688                 started = STARTED_NONE;
2689                 if(suppressKibitz) next_out = i+1;
2690             }
2691
2692             /* Kludge to deal with rcmd protocol */
2693             if (firstTime && looking_at(buf, &i, "\001*")) {
2694                 DisplayFatalError(&buf[1], 0, 1);
2695                 continue;
2696             } else {
2697                 firstTime = FALSE;
2698             }
2699
2700             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2701                 ics_type = ICS_ICC;
2702                 ics_prefix = "/";
2703                 if (appData.debugMode)
2704                   fprintf(debugFP, "ics_type %d\n", ics_type);
2705                 continue;
2706             }
2707             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2708                 ics_type = ICS_FICS;
2709                 ics_prefix = "$";
2710                 if (appData.debugMode)
2711                   fprintf(debugFP, "ics_type %d\n", ics_type);
2712                 continue;
2713             }
2714             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2715                 ics_type = ICS_CHESSNET;
2716                 ics_prefix = "/";
2717                 if (appData.debugMode)
2718                   fprintf(debugFP, "ics_type %d\n", ics_type);
2719                 continue;
2720             }
2721
2722             if (!loggedOn &&
2723                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2724                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2725                  looking_at(buf, &i, "will be \"*\""))) {
2726               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2727               continue;
2728             }
2729
2730             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2731               char buf[MSG_SIZ];
2732               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2733               DisplayIcsInteractionTitle(buf);
2734               have_set_title = TRUE;
2735             }
2736
2737             /* skip finger notes */
2738             if (started == STARTED_NONE &&
2739                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2740                  (buf[i] == '1' && buf[i+1] == '0')) &&
2741                 buf[i+2] == ':' && buf[i+3] == ' ') {
2742               started = STARTED_CHATTER;
2743               i += 3;
2744               continue;
2745             }
2746
2747             oldi = i;
2748             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2749             if(appData.seekGraph) {
2750                 if(soughtPending && MatchSoughtLine(buf+i)) {
2751                     i = strstr(buf+i, "rated") - buf;
2752                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753                     next_out = leftover_start = i;
2754                     started = STARTED_CHATTER;
2755                     suppressKibitz = TRUE;
2756                     continue;
2757                 }
2758                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2759                         && looking_at(buf, &i, "* ads displayed")) {
2760                     soughtPending = FALSE;
2761                     seekGraphUp = TRUE;
2762                     DrawSeekGraph();
2763                     continue;
2764                 }
2765                 if(appData.autoRefresh) {
2766                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2767                         int s = (ics_type == ICS_ICC); // ICC format differs
2768                         if(seekGraphUp)
2769                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2770                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2771                         looking_at(buf, &i, "*% "); // eat prompt
2772                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2773                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2774                         next_out = i; // suppress
2775                         continue;
2776                     }
2777                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2778                         char *p = star_match[0];
2779                         while(*p) {
2780                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2781                             while(*p && *p++ != ' '); // next
2782                         }
2783                         looking_at(buf, &i, "*% "); // eat prompt
2784                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785                         next_out = i;
2786                         continue;
2787                     }
2788                 }
2789             }
2790
2791             /* skip formula vars */
2792             if (started == STARTED_NONE &&
2793                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2794               started = STARTED_CHATTER;
2795               i += 3;
2796               continue;
2797             }
2798
2799             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2800             if (appData.autoKibitz && started == STARTED_NONE &&
2801                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2802                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2803                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2804                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2805                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2806                         suppressKibitz = TRUE;
2807                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2808                         next_out = i;
2809                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2810                                 && (gameMode == IcsPlayingWhite)) ||
2811                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2812                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2813                             started = STARTED_CHATTER; // own kibitz we simply discard
2814                         else {
2815                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2816                             parse_pos = 0; parse[0] = NULLCHAR;
2817                             savingComment = TRUE;
2818                             suppressKibitz = gameMode != IcsObserving ? 2 :
2819                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2820                         }
2821                         continue;
2822                 } else
2823                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2824                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2825                          && atoi(star_match[0])) {
2826                     // suppress the acknowledgements of our own autoKibitz
2827                     char *p;
2828                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2830                     SendToPlayer(star_match[0], strlen(star_match[0]));
2831                     if(looking_at(buf, &i, "*% ")) // eat prompt
2832                         suppressKibitz = FALSE;
2833                     next_out = i;
2834                     continue;
2835                 }
2836             } // [HGM] kibitz: end of patch
2837
2838             // [HGM] chat: intercept tells by users for which we have an open chat window
2839             channel = -1;
2840             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2841                                            looking_at(buf, &i, "* whispers:") ||
2842                                            looking_at(buf, &i, "* kibitzes:") ||
2843                                            looking_at(buf, &i, "* shouts:") ||
2844                                            looking_at(buf, &i, "* c-shouts:") ||
2845                                            looking_at(buf, &i, "--> * ") ||
2846                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2847                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2848                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2849                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2850                 int p;
2851                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2852                 chattingPartner = -1;
2853
2854                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2855                 for(p=0; p<MAX_CHAT; p++) {
2856                     if(channel == atoi(chatPartner[p])) {
2857                     talker[0] = '['; strcat(talker, "] ");
2858                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2859                     chattingPartner = p; break;
2860                     }
2861                 } else
2862                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(!strcmp("kibitzes", chatPartner[p])) {
2865                         talker[0] = '['; strcat(talker, "] ");
2866                         chattingPartner = p; break;
2867                     }
2868                 } else
2869                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2870                 for(p=0; p<MAX_CHAT; p++) {
2871                     if(!strcmp("whispers", chatPartner[p])) {
2872                         talker[0] = '['; strcat(talker, "] ");
2873                         chattingPartner = p; break;
2874                     }
2875                 } else
2876                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2877                   if(buf[i-8] == '-' && buf[i-3] == 't')
2878                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2879                     if(!strcmp("c-shouts", chatPartner[p])) {
2880                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2881                         chattingPartner = p; break;
2882                     }
2883                   }
2884                   if(chattingPartner < 0)
2885                   for(p=0; p<MAX_CHAT; p++) {
2886                     if(!strcmp("shouts", chatPartner[p])) {
2887                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2888                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2889                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2890                         chattingPartner = p; break;
2891                     }
2892                   }
2893                 }
2894                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2895                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2896                     talker[0] = 0; Colorize(ColorTell, FALSE);
2897                     chattingPartner = p; break;
2898                 }
2899                 if(chattingPartner<0) i = oldi; else {
2900                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2901                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2902                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2903                     started = STARTED_COMMENT;
2904                     parse_pos = 0; parse[0] = NULLCHAR;
2905                     savingComment = 3 + chattingPartner; // counts as TRUE
2906                     suppressKibitz = TRUE;
2907                     continue;
2908                 }
2909             } // [HGM] chat: end of patch
2910
2911             if (appData.zippyTalk || appData.zippyPlay) {
2912                 /* [DM] Backup address for color zippy lines */
2913                 backup = i;
2914 #if ZIPPY
2915                if (loggedOn == TRUE)
2916                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2917                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2918 #endif
2919             } // [DM] 'else { ' deleted
2920                 if (
2921                     /* Regular tells and says */
2922                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2923                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2924                     looking_at(buf, &i, "* says: ") ||
2925                     /* Don't color "message" or "messages" output */
2926                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2927                     looking_at(buf, &i, "*. * at *:*: ") ||
2928                     looking_at(buf, &i, "--* (*:*): ") ||
2929                     /* Message notifications (same color as tells) */
2930                     looking_at(buf, &i, "* has left a message ") ||
2931                     looking_at(buf, &i, "* just sent you a message:\n") ||
2932                     /* Whispers and kibitzes */
2933                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2934                     looking_at(buf, &i, "* kibitzes: ") ||
2935                     /* Channel tells */
2936                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2937
2938                   if (tkind == 1 && strchr(star_match[0], ':')) {
2939                       /* Avoid "tells you:" spoofs in channels */
2940                      tkind = 3;
2941                   }
2942                   if (star_match[0][0] == NULLCHAR ||
2943                       strchr(star_match[0], ' ') ||
2944                       (tkind == 3 && strchr(star_match[1], ' '))) {
2945                     /* Reject bogus matches */
2946                     i = oldi;
2947                   } else {
2948                     if (appData.colorize) {
2949                       if (oldi > next_out) {
2950                         SendToPlayer(&buf[next_out], oldi - next_out);
2951                         next_out = oldi;
2952                       }
2953                       switch (tkind) {
2954                       case 1:
2955                         Colorize(ColorTell, FALSE);
2956                         curColor = ColorTell;
2957                         break;
2958                       case 2:
2959                         Colorize(ColorKibitz, FALSE);
2960                         curColor = ColorKibitz;
2961                         break;
2962                       case 3:
2963                         p = strrchr(star_match[1], '(');
2964                         if (p == NULL) {
2965                           p = star_match[1];
2966                         } else {
2967                           p++;
2968                         }
2969                         if (atoi(p) == 1) {
2970                           Colorize(ColorChannel1, FALSE);
2971                           curColor = ColorChannel1;
2972                         } else {
2973                           Colorize(ColorChannel, FALSE);
2974                           curColor = ColorChannel;
2975                         }
2976                         break;
2977                       case 5:
2978                         curColor = ColorNormal;
2979                         break;
2980                       }
2981                     }
2982                     if (started == STARTED_NONE && appData.autoComment &&
2983                         (gameMode == IcsObserving ||
2984                          gameMode == IcsPlayingWhite ||
2985                          gameMode == IcsPlayingBlack)) {
2986                       parse_pos = i - oldi;
2987                       memcpy(parse, &buf[oldi], parse_pos);
2988                       parse[parse_pos] = NULLCHAR;
2989                       started = STARTED_COMMENT;
2990                       savingComment = TRUE;
2991                     } else {
2992                       started = STARTED_CHATTER;
2993                       savingComment = FALSE;
2994                     }
2995                     loggedOn = TRUE;
2996                     continue;
2997                   }
2998                 }
2999
3000                 if (looking_at(buf, &i, "* s-shouts: ") ||
3001                     looking_at(buf, &i, "* c-shouts: ")) {
3002                     if (appData.colorize) {
3003                         if (oldi > next_out) {
3004                             SendToPlayer(&buf[next_out], oldi - next_out);
3005                             next_out = oldi;
3006                         }
3007                         Colorize(ColorSShout, FALSE);
3008                         curColor = ColorSShout;
3009                     }
3010                     loggedOn = TRUE;
3011                     started = STARTED_CHATTER;
3012                     continue;
3013                 }
3014
3015                 if (looking_at(buf, &i, "--->")) {
3016                     loggedOn = TRUE;
3017                     continue;
3018                 }
3019
3020                 if (looking_at(buf, &i, "* shouts: ") ||
3021                     looking_at(buf, &i, "--> ")) {
3022                     if (appData.colorize) {
3023                         if (oldi > next_out) {
3024                             SendToPlayer(&buf[next_out], oldi - next_out);
3025                             next_out = oldi;
3026                         }
3027                         Colorize(ColorShout, FALSE);
3028                         curColor = ColorShout;
3029                     }
3030                     loggedOn = TRUE;
3031                     started = STARTED_CHATTER;
3032                     continue;
3033                 }
3034
3035                 if (looking_at( buf, &i, "Challenge:")) {
3036                     if (appData.colorize) {
3037                         if (oldi > next_out) {
3038                             SendToPlayer(&buf[next_out], oldi - next_out);
3039                             next_out = oldi;
3040                         }
3041                         Colorize(ColorChallenge, FALSE);
3042                         curColor = ColorChallenge;
3043                     }
3044                     loggedOn = TRUE;
3045                     continue;
3046                 }
3047
3048                 if (looking_at(buf, &i, "* offers you") ||
3049                     looking_at(buf, &i, "* offers to be") ||
3050                     looking_at(buf, &i, "* would like to") ||
3051                     looking_at(buf, &i, "* requests to") ||
3052                     looking_at(buf, &i, "Your opponent offers") ||
3053                     looking_at(buf, &i, "Your opponent requests")) {
3054
3055                     if (appData.colorize) {
3056                         if (oldi > next_out) {
3057                             SendToPlayer(&buf[next_out], oldi - next_out);
3058                             next_out = oldi;
3059                         }
3060                         Colorize(ColorRequest, FALSE);
3061                         curColor = ColorRequest;
3062                     }
3063                     continue;
3064                 }
3065
3066                 if (looking_at(buf, &i, "* (*) seeking")) {
3067                     if (appData.colorize) {
3068                         if (oldi > next_out) {
3069                             SendToPlayer(&buf[next_out], oldi - next_out);
3070                             next_out = oldi;
3071                         }
3072                         Colorize(ColorSeek, FALSE);
3073                         curColor = ColorSeek;
3074                     }
3075                     continue;
3076             }
3077
3078             if (looking_at(buf, &i, "\\   ")) {
3079                 if (prevColor != ColorNormal) {
3080                     if (oldi > next_out) {
3081                         SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = oldi;
3083                     }
3084                     Colorize(prevColor, TRUE);
3085                     curColor = prevColor;
3086                 }
3087                 if (savingComment) {
3088                     parse_pos = i - oldi;
3089                     memcpy(parse, &buf[oldi], parse_pos);
3090                     parse[parse_pos] = NULLCHAR;
3091                     started = STARTED_COMMENT;
3092                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3093                         chattingPartner = savingComment - 3; // kludge to remember the box
3094                 } else {
3095                     started = STARTED_CHATTER;
3096                 }
3097                 continue;
3098             }
3099
3100             if (looking_at(buf, &i, "Black Strength :") ||
3101                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3102                 looking_at(buf, &i, "<10>") ||
3103                 looking_at(buf, &i, "#@#")) {
3104                 /* Wrong board style */
3105                 loggedOn = TRUE;
3106                 SendToICS(ics_prefix);
3107                 SendToICS("set style 12\n");
3108                 SendToICS(ics_prefix);
3109                 SendToICS("refresh\n");
3110                 continue;
3111             }
3112
3113             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3114                 ICSInitScript();
3115                 have_sent_ICS_logon = 1;
3116                 continue;
3117             }
3118
3119             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3120                 (looking_at(buf, &i, "\n<12> ") ||
3121                  looking_at(buf, &i, "<12> "))) {
3122                 loggedOn = TRUE;
3123                 if (oldi > next_out) {
3124                     SendToPlayer(&buf[next_out], oldi - next_out);
3125                 }
3126                 next_out = i;
3127                 started = STARTED_BOARD;
3128                 parse_pos = 0;
3129                 continue;
3130             }
3131
3132             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3133                 looking_at(buf, &i, "<b1> ")) {
3134                 if (oldi > next_out) {
3135                     SendToPlayer(&buf[next_out], oldi - next_out);
3136                 }
3137                 next_out = i;
3138                 started = STARTED_HOLDINGS;
3139                 parse_pos = 0;
3140                 continue;
3141             }
3142
3143             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3144                 loggedOn = TRUE;
3145                 /* Header for a move list -- first line */
3146
3147                 switch (ics_getting_history) {
3148                   case H_FALSE:
3149                     switch (gameMode) {
3150                       case IcsIdle:
3151                       case BeginningOfGame:
3152                         /* User typed "moves" or "oldmoves" while we
3153                            were idle.  Pretend we asked for these
3154                            moves and soak them up so user can step
3155                            through them and/or save them.
3156                            */
3157                         Reset(FALSE, TRUE);
3158                         gameMode = IcsObserving;
3159                         ModeHighlight();
3160                         ics_gamenum = -1;
3161                         ics_getting_history = H_GOT_UNREQ_HEADER;
3162                         break;
3163                       case EditGame: /*?*/
3164                       case EditPosition: /*?*/
3165                         /* Should above feature work in these modes too? */
3166                         /* For now it doesn't */
3167                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3168                         break;
3169                       default:
3170                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3171                         break;
3172                     }
3173                     break;
3174                   case H_REQUESTED:
3175                     /* Is this the right one? */
3176                     if (gameInfo.white && gameInfo.black &&
3177                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3178                         strcmp(gameInfo.black, star_match[2]) == 0) {
3179                         /* All is well */
3180                         ics_getting_history = H_GOT_REQ_HEADER;
3181                     }
3182                     break;
3183                   case H_GOT_REQ_HEADER:
3184                   case H_GOT_UNREQ_HEADER:
3185                   case H_GOT_UNWANTED_HEADER:
3186                   case H_GETTING_MOVES:
3187                     /* Should not happen */
3188                     DisplayError(_("Error gathering move list: two headers"), 0);
3189                     ics_getting_history = H_FALSE;
3190                     break;
3191                 }
3192
3193                 /* Save player ratings into gameInfo if needed */
3194                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3195                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3196                     (gameInfo.whiteRating == -1 ||
3197                      gameInfo.blackRating == -1)) {
3198
3199                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3200                     gameInfo.blackRating = string_to_rating(star_match[3]);
3201                     if (appData.debugMode)
3202                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3203                               gameInfo.whiteRating, gameInfo.blackRating);
3204                 }
3205                 continue;
3206             }
3207
3208             if (looking_at(buf, &i,
3209               "* * match, initial time: * minute*, increment: * second")) {
3210                 /* Header for a move list -- second line */
3211                 /* Initial board will follow if this is a wild game */
3212                 if (gameInfo.event != NULL) free(gameInfo.event);
3213                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3214                 gameInfo.event = StrSave(str);
3215                 /* [HGM] we switched variant. Translate boards if needed. */
3216                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3217                 continue;
3218             }
3219
3220             if (looking_at(buf, &i, "Move  ")) {
3221                 /* Beginning of a move list */
3222                 switch (ics_getting_history) {
3223                   case H_FALSE:
3224                     /* Normally should not happen */
3225                     /* Maybe user hit reset while we were parsing */
3226                     break;
3227                   case H_REQUESTED:
3228                     /* Happens if we are ignoring a move list that is not
3229                      * the one we just requested.  Common if the user
3230                      * tries to observe two games without turning off
3231                      * getMoveList */
3232                     break;
3233                   case H_GETTING_MOVES:
3234                     /* Should not happen */
3235                     DisplayError(_("Error gathering move list: nested"), 0);
3236                     ics_getting_history = H_FALSE;
3237                     break;
3238                   case H_GOT_REQ_HEADER:
3239                     ics_getting_history = H_GETTING_MOVES;
3240                     started = STARTED_MOVES;
3241                     parse_pos = 0;
3242                     if (oldi > next_out) {
3243                         SendToPlayer(&buf[next_out], oldi - next_out);
3244                     }
3245                     break;
3246                   case H_GOT_UNREQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES_NOHIDE;
3249                     parse_pos = 0;
3250                     break;
3251                   case H_GOT_UNWANTED_HEADER:
3252                     ics_getting_history = H_FALSE;
3253                     break;
3254                 }
3255                 continue;
3256             }
3257
3258             if (looking_at(buf, &i, "% ") ||
3259                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3260                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3261                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3262                     soughtPending = FALSE;
3263                     seekGraphUp = TRUE;
3264                     DrawSeekGraph();
3265                 }
3266                 if(suppressKibitz) next_out = i;
3267                 savingComment = FALSE;
3268                 suppressKibitz = 0;
3269                 switch (started) {
3270                   case STARTED_MOVES:
3271                   case STARTED_MOVES_NOHIDE:
3272                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3273                     parse[parse_pos + i - oldi] = NULLCHAR;
3274                     ParseGameHistory(parse);
3275 #if ZIPPY
3276                     if (appData.zippyPlay && first.initDone) {
3277                         FeedMovesToProgram(&first, forwardMostMove);
3278                         if (gameMode == IcsPlayingWhite) {
3279                             if (WhiteOnMove(forwardMostMove)) {
3280                                 if (first.sendTime) {
3281                                   if (first.useColors) {
3282                                     SendToProgram("black\n", &first);
3283                                   }
3284                                   SendTimeRemaining(&first, TRUE);
3285                                 }
3286                                 if (first.useColors) {
3287                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3288                                 }
3289                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3290                                 first.maybeThinking = TRUE;
3291                             } else {
3292                                 if (first.usePlayother) {
3293                                   if (first.sendTime) {
3294                                     SendTimeRemaining(&first, TRUE);
3295                                   }
3296                                   SendToProgram("playother\n", &first);
3297                                   firstMove = FALSE;
3298                                 } else {
3299                                   firstMove = TRUE;
3300                                 }
3301                             }
3302                         } else if (gameMode == IcsPlayingBlack) {
3303                             if (!WhiteOnMove(forwardMostMove)) {
3304                                 if (first.sendTime) {
3305                                   if (first.useColors) {
3306                                     SendToProgram("white\n", &first);
3307                                   }
3308                                   SendTimeRemaining(&first, FALSE);
3309                                 }
3310                                 if (first.useColors) {
3311                                   SendToProgram("black\n", &first);
3312                                 }
3313                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3314                                 first.maybeThinking = TRUE;
3315                             } else {
3316                                 if (first.usePlayother) {
3317                                   if (first.sendTime) {
3318                                     SendTimeRemaining(&first, FALSE);
3319                                   }
3320                                   SendToProgram("playother\n", &first);
3321                                   firstMove = FALSE;
3322                                 } else {
3323                                   firstMove = TRUE;
3324                                 }
3325                             }
3326                         }
3327                     }
3328 #endif
3329                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3330                         /* Moves came from oldmoves or moves command
3331                            while we weren't doing anything else.
3332                            */
3333                         currentMove = forwardMostMove;
3334                         ClearHighlights();/*!!could figure this out*/
3335                         flipView = appData.flipView;
3336                         DrawPosition(TRUE, boards[currentMove]);
3337                         DisplayBothClocks();
3338                         snprintf(str, MSG_SIZ, "%s vs. %s",
3339                                 gameInfo.white, gameInfo.black);
3340                         DisplayTitle(str);
3341                         gameMode = IcsIdle;
3342                     } else {
3343                         /* Moves were history of an active game */
3344                         if (gameInfo.resultDetails != NULL) {
3345                             free(gameInfo.resultDetails);
3346                             gameInfo.resultDetails = NULL;
3347                         }
3348                     }
3349                     HistorySet(parseList, backwardMostMove,
3350                                forwardMostMove, currentMove-1);
3351                     DisplayMove(currentMove - 1);
3352                     if (started == STARTED_MOVES) next_out = i;
3353                     started = STARTED_NONE;
3354                     ics_getting_history = H_FALSE;
3355                     break;
3356
3357                   case STARTED_OBSERVE:
3358                     started = STARTED_NONE;
3359                     SendToICS(ics_prefix);
3360                     SendToICS("refresh\n");
3361                     break;
3362
3363                   default:
3364                     break;
3365                 }
3366                 if(bookHit) { // [HGM] book: simulate book reply
3367                     static char bookMove[MSG_SIZ]; // a bit generous?
3368
3369                     programStats.nodes = programStats.depth = programStats.time =
3370                     programStats.score = programStats.got_only_move = 0;
3371                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3372
3373                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3374                     strcat(bookMove, bookHit);
3375                     HandleMachineMove(bookMove, &first);
3376                 }
3377                 continue;
3378             }
3379
3380             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3381                  started == STARTED_HOLDINGS ||
3382                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3383                 /* Accumulate characters in move list or board */
3384                 parse[parse_pos++] = buf[i];
3385             }
3386
3387             /* Start of game messages.  Mostly we detect start of game
3388                when the first board image arrives.  On some versions
3389                of the ICS, though, we need to do a "refresh" after starting
3390                to observe in order to get the current board right away. */
3391             if (looking_at(buf, &i, "Adding game * to observation list")) {
3392                 started = STARTED_OBSERVE;
3393                 continue;
3394             }
3395
3396             /* Handle auto-observe */
3397             if (appData.autoObserve &&
3398                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3399                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3400                 char *player;
3401                 /* Choose the player that was highlighted, if any. */
3402                 if (star_match[0][0] == '\033' ||
3403                     star_match[1][0] != '\033') {
3404                     player = star_match[0];
3405                 } else {
3406                     player = star_match[2];
3407                 }
3408                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3409                         ics_prefix, StripHighlightAndTitle(player));
3410                 SendToICS(str);
3411
3412                 /* Save ratings from notify string */
3413                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3414                 player1Rating = string_to_rating(star_match[1]);
3415                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3416                 player2Rating = string_to_rating(star_match[3]);
3417
3418                 if (appData.debugMode)
3419                   fprintf(debugFP,
3420                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3421                           player1Name, player1Rating,
3422                           player2Name, player2Rating);
3423
3424                 continue;
3425             }
3426
3427             /* Deal with automatic examine mode after a game,
3428                and with IcsObserving -> IcsExamining transition */
3429             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3430                 looking_at(buf, &i, "has made you an examiner of game *")) {
3431
3432                 int gamenum = atoi(star_match[0]);
3433                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3434                     gamenum == ics_gamenum) {
3435                     /* We were already playing or observing this game;
3436                        no need to refetch history */
3437                     gameMode = IcsExamining;
3438                     if (pausing) {
3439                         pauseExamForwardMostMove = forwardMostMove;
3440                     } else if (currentMove < forwardMostMove) {
3441                         ForwardInner(forwardMostMove);
3442                     }
3443                 } else {
3444                     /* I don't think this case really can happen */
3445                     SendToICS(ics_prefix);
3446                     SendToICS("refresh\n");
3447                 }
3448                 continue;
3449             }
3450
3451             /* Error messages */
3452 //          if (ics_user_moved) {
3453             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3454                 if (looking_at(buf, &i, "Illegal move") ||
3455                     looking_at(buf, &i, "Not a legal move") ||
3456                     looking_at(buf, &i, "Your king is in check") ||
3457                     looking_at(buf, &i, "It isn't your turn") ||
3458                     looking_at(buf, &i, "It is not your move")) {
3459                     /* Illegal move */
3460                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3461                         currentMove = forwardMostMove-1;
3462                         DisplayMove(currentMove - 1); /* before DMError */
3463                         DrawPosition(FALSE, boards[currentMove]);
3464                         SwitchClocks(forwardMostMove-1); // [HGM] race
3465                         DisplayBothClocks();
3466                     }
3467                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3468                     ics_user_moved = 0;
3469                     continue;
3470                 }
3471             }
3472
3473             if (looking_at(buf, &i, "still have time") ||
3474                 looking_at(buf, &i, "not out of time") ||
3475                 looking_at(buf, &i, "either player is out of time") ||
3476                 looking_at(buf, &i, "has timeseal; checking")) {
3477                 /* We must have called his flag a little too soon */
3478                 whiteFlag = blackFlag = FALSE;
3479                 continue;
3480             }
3481
3482             if (looking_at(buf, &i, "added * seconds to") ||
3483                 looking_at(buf, &i, "seconds were added to")) {
3484                 /* Update the clocks */
3485                 SendToICS(ics_prefix);
3486                 SendToICS("refresh\n");
3487                 continue;
3488             }
3489
3490             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3491                 ics_clock_paused = TRUE;
3492                 StopClocks();
3493                 continue;
3494             }
3495
3496             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3497                 ics_clock_paused = FALSE;
3498                 StartClocks();
3499                 continue;
3500             }
3501
3502             /* Grab player ratings from the Creating: message.
3503                Note we have to check for the special case when
3504                the ICS inserts things like [white] or [black]. */
3505             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3506                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3507                 /* star_matches:
3508                    0    player 1 name (not necessarily white)
3509                    1    player 1 rating
3510                    2    empty, white, or black (IGNORED)
3511                    3    player 2 name (not necessarily black)
3512                    4    player 2 rating
3513
3514                    The names/ratings are sorted out when the game
3515                    actually starts (below).
3516                 */
3517                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3518                 player1Rating = string_to_rating(star_match[1]);
3519                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3520                 player2Rating = string_to_rating(star_match[4]);
3521
3522                 if (appData.debugMode)
3523                   fprintf(debugFP,
3524                           "Ratings from 'Creating:' %s %d, %s %d\n",
3525                           player1Name, player1Rating,
3526                           player2Name, player2Rating);
3527
3528                 continue;
3529             }
3530
3531             /* Improved generic start/end-of-game messages */
3532             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3533                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3534                 /* If tkind == 0: */
3535                 /* star_match[0] is the game number */
3536                 /*           [1] is the white player's name */
3537                 /*           [2] is the black player's name */
3538                 /* For end-of-game: */
3539                 /*           [3] is the reason for the game end */
3540                 /*           [4] is a PGN end game-token, preceded by " " */
3541                 /* For start-of-game: */
3542                 /*           [3] begins with "Creating" or "Continuing" */
3543                 /*           [4] is " *" or empty (don't care). */
3544                 int gamenum = atoi(star_match[0]);
3545                 char *whitename, *blackname, *why, *endtoken;
3546                 ChessMove endtype = EndOfFile;
3547
3548                 if (tkind == 0) {
3549                   whitename = star_match[1];
3550                   blackname = star_match[2];
3551                   why = star_match[3];
3552                   endtoken = star_match[4];
3553                 } else {
3554                   whitename = star_match[1];
3555                   blackname = star_match[3];
3556                   why = star_match[5];
3557                   endtoken = star_match[6];
3558                 }
3559
3560                 /* Game start messages */
3561                 if (strncmp(why, "Creating ", 9) == 0 ||
3562                     strncmp(why, "Continuing ", 11) == 0) {
3563                     gs_gamenum = gamenum;
3564                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3565                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3566 #if ZIPPY
3567                     if (appData.zippyPlay) {
3568                         ZippyGameStart(whitename, blackname);
3569                     }
3570 #endif /*ZIPPY*/
3571                     partnerBoardValid = FALSE; // [HGM] bughouse
3572                     continue;
3573                 }
3574
3575                 /* Game end messages */
3576                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3577                     ics_gamenum != gamenum) {
3578                     continue;
3579                 }
3580                 while (endtoken[0] == ' ') endtoken++;
3581                 switch (endtoken[0]) {
3582                   case '*':
3583                   default:
3584                     endtype = GameUnfinished;
3585                     break;
3586                   case '0':
3587                     endtype = BlackWins;
3588                     break;
3589                   case '1':
3590                     if (endtoken[1] == '/')
3591                       endtype = GameIsDrawn;
3592                     else
3593                       endtype = WhiteWins;
3594                     break;
3595                 }
3596                 GameEnds(endtype, why, GE_ICS);
3597 #if ZIPPY
3598                 if (appData.zippyPlay && first.initDone) {
3599                     ZippyGameEnd(endtype, why);
3600                     if (first.pr == NULL) {
3601                       /* Start the next process early so that we'll
3602                          be ready for the next challenge */
3603                       StartChessProgram(&first);
3604                     }
3605                     /* Send "new" early, in case this command takes
3606                        a long time to finish, so that we'll be ready
3607                        for the next challenge. */
3608                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3609                     Reset(TRUE, TRUE);
3610                 }
3611 #endif /*ZIPPY*/
3612                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3613                 continue;
3614             }
3615
3616             if (looking_at(buf, &i, "Removing game * from observation") ||
3617                 looking_at(buf, &i, "no longer observing game *") ||
3618                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3619                 if (gameMode == IcsObserving &&
3620                     atoi(star_match[0]) == ics_gamenum)
3621                   {
3622                       /* icsEngineAnalyze */
3623                       if (appData.icsEngineAnalyze) {
3624                             ExitAnalyzeMode();
3625                             ModeHighlight();
3626                       }
3627                       StopClocks();
3628                       gameMode = IcsIdle;
3629                       ics_gamenum = -1;
3630                       ics_user_moved = FALSE;
3631                   }
3632                 continue;
3633             }
3634
3635             if (looking_at(buf, &i, "no longer examining game *")) {
3636                 if (gameMode == IcsExamining &&
3637                     atoi(star_match[0]) == ics_gamenum)
3638                   {
3639                       gameMode = IcsIdle;
3640                       ics_gamenum = -1;
3641                       ics_user_moved = FALSE;
3642                   }
3643                 continue;
3644             }
3645
3646             /* Advance leftover_start past any newlines we find,
3647                so only partial lines can get reparsed */
3648             if (looking_at(buf, &i, "\n")) {
3649                 prevColor = curColor;
3650                 if (curColor != ColorNormal) {
3651                     if (oldi > next_out) {
3652                         SendToPlayer(&buf[next_out], oldi - next_out);
3653                         next_out = oldi;
3654                     }
3655                     Colorize(ColorNormal, FALSE);
3656                     curColor = ColorNormal;
3657                 }
3658                 if (started == STARTED_BOARD) {
3659                     started = STARTED_NONE;
3660                     parse[parse_pos] = NULLCHAR;
3661                     ParseBoard12(parse);
3662                     ics_user_moved = 0;
3663
3664                     /* Send premove here */
3665                     if (appData.premove) {
3666                       char str[MSG_SIZ];
3667                       if (currentMove == 0 &&
3668                           gameMode == IcsPlayingWhite &&
3669                           appData.premoveWhite) {
3670                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3671                         if (appData.debugMode)
3672                           fprintf(debugFP, "Sending premove:\n");
3673                         SendToICS(str);
3674                       } else if (currentMove == 1 &&
3675                                  gameMode == IcsPlayingBlack &&
3676                                  appData.premoveBlack) {
3677                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3678                         if (appData.debugMode)
3679                           fprintf(debugFP, "Sending premove:\n");
3680                         SendToICS(str);
3681                       } else if (gotPremove) {
3682                         gotPremove = 0;
3683                         ClearPremoveHighlights();
3684                         if (appData.debugMode)
3685                           fprintf(debugFP, "Sending premove:\n");
3686                           UserMoveEvent(premoveFromX, premoveFromY,
3687                                         premoveToX, premoveToY,
3688                                         premovePromoChar);
3689                       }
3690                     }
3691
3692                     /* Usually suppress following prompt */
3693                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3694                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3695                         if (looking_at(buf, &i, "*% ")) {
3696                             savingComment = FALSE;
3697                             suppressKibitz = 0;
3698                         }
3699                     }
3700                     next_out = i;
3701                 } else if (started == STARTED_HOLDINGS) {
3702                     int gamenum;
3703                     char new_piece[MSG_SIZ];
3704                     started = STARTED_NONE;
3705                     parse[parse_pos] = NULLCHAR;
3706                     if (appData.debugMode)
3707                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3708                                                         parse, currentMove);
3709                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3710                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3711                         if (gameInfo.variant == VariantNormal) {
3712                           /* [HGM] We seem to switch variant during a game!
3713                            * Presumably no holdings were displayed, so we have
3714                            * to move the position two files to the right to
3715                            * create room for them!
3716                            */
3717                           VariantClass newVariant;
3718                           switch(gameInfo.boardWidth) { // base guess on board width
3719                                 case 9:  newVariant = VariantShogi; break;
3720                                 case 10: newVariant = VariantGreat; break;
3721                                 default: newVariant = VariantCrazyhouse; break;
3722                           }
3723                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3724                           /* Get a move list just to see the header, which
3725                              will tell us whether this is really bug or zh */
3726                           if (ics_getting_history == H_FALSE) {
3727                             ics_getting_history = H_REQUESTED;
3728                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3729                             SendToICS(str);
3730                           }
3731                         }
3732                         new_piece[0] = NULLCHAR;
3733                         sscanf(parse, "game %d white [%s black [%s <- %s",
3734                                &gamenum, white_holding, black_holding,
3735                                new_piece);
3736                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3737                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3738                         /* [HGM] copy holdings to board holdings area */
3739                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3740                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3741                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3742 #if ZIPPY
3743                         if (appData.zippyPlay && first.initDone) {
3744                             ZippyHoldings(white_holding, black_holding,
3745                                           new_piece);
3746                         }
3747 #endif /*ZIPPY*/
3748                         if (tinyLayout || smallLayout) {
3749                             char wh[16], bh[16];
3750                             PackHolding(wh, white_holding);
3751                             PackHolding(bh, black_holding);
3752                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3753                                     gameInfo.white, gameInfo.black);
3754                         } else {
3755                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3756                                     gameInfo.white, white_holding,
3757                                     gameInfo.black, black_holding);
3758                         }
3759                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3760                         DrawPosition(FALSE, boards[currentMove]);
3761                         DisplayTitle(str);
3762                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3763                         sscanf(parse, "game %d white [%s black [%s <- %s",
3764                                &gamenum, white_holding, black_holding,
3765                                new_piece);
3766                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3767                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3768                         /* [HGM] copy holdings to partner-board holdings area */
3769                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3770                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3771                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3772                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3773                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3774                       }
3775                     }
3776                     /* Suppress following prompt */
3777                     if (looking_at(buf, &i, "*% ")) {
3778                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3779                         savingComment = FALSE;
3780                         suppressKibitz = 0;
3781                     }
3782                     next_out = i;
3783                 }
3784                 continue;
3785             }
3786
3787             i++;                /* skip unparsed character and loop back */
3788         }
3789
3790         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3791 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3792 //          SendToPlayer(&buf[next_out], i - next_out);
3793             started != STARTED_HOLDINGS && leftover_start > next_out) {
3794             SendToPlayer(&buf[next_out], leftover_start - next_out);
3795             next_out = i;
3796         }
3797
3798         leftover_len = buf_len - leftover_start;
3799         /* if buffer ends with something we couldn't parse,
3800            reparse it after appending the next read */
3801
3802     } else if (count == 0) {
3803         RemoveInputSource(isr);
3804         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3805     } else {
3806         DisplayFatalError(_("Error reading from ICS"), error, 1);
3807     }
3808 }
3809
3810
3811 /* Board style 12 looks like this:
3812
3813    <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
3814
3815  * The "<12> " is stripped before it gets to this routine.  The two
3816  * trailing 0's (flip state and clock ticking) are later addition, and
3817  * some chess servers may not have them, or may have only the first.
3818  * Additional trailing fields may be added in the future.
3819  */
3820
3821 #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"
3822
3823 #define RELATION_OBSERVING_PLAYED    0
3824 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3825 #define RELATION_PLAYING_MYMOVE      1
3826 #define RELATION_PLAYING_NOTMYMOVE  -1
3827 #define RELATION_EXAMINING           2
3828 #define RELATION_ISOLATED_BOARD     -3
3829 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3830
3831 void
3832 ParseBoard12(string)
3833      char *string;
3834 {
3835     GameMode newGameMode;
3836     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3837     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3838     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3839     char to_play, board_chars[200];
3840     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3841     char black[32], white[32];
3842     Board board;
3843     int prevMove = currentMove;
3844     int ticking = 2;
3845     ChessMove moveType;
3846     int fromX, fromY, toX, toY;
3847     char promoChar;
3848     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3849     char *bookHit = NULL; // [HGM] book
3850     Boolean weird = FALSE, reqFlag = FALSE;
3851
3852     fromX = fromY = toX = toY = -1;
3853
3854     newGame = FALSE;
3855
3856     if (appData.debugMode)
3857       fprintf(debugFP, _("Parsing board: %s\n"), string);
3858
3859     move_str[0] = NULLCHAR;
3860     elapsed_time[0] = NULLCHAR;
3861     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3862         int  i = 0, j;
3863         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3864             if(string[i] == ' ') { ranks++; files = 0; }
3865             else files++;
3866             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3867             i++;
3868         }
3869         for(j = 0; j <i; j++) board_chars[j] = string[j];
3870         board_chars[i] = '\0';
3871         string += i + 1;
3872     }
3873     n = sscanf(string, PATTERN, &to_play, &double_push,
3874                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3875                &gamenum, white, black, &relation, &basetime, &increment,
3876                &white_stren, &black_stren, &white_time, &black_time,
3877                &moveNum, str, elapsed_time, move_str, &ics_flip,
3878                &ticking);
3879
3880     if (n < 21) {
3881         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3882         DisplayError(str, 0);
3883         return;
3884     }
3885
3886     /* Convert the move number to internal form */
3887     moveNum = (moveNum - 1) * 2;
3888     if (to_play == 'B') moveNum++;
3889     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3890       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3891                         0, 1);
3892       return;
3893     }
3894
3895     switch (relation) {
3896       case RELATION_OBSERVING_PLAYED:
3897       case RELATION_OBSERVING_STATIC:
3898         if (gamenum == -1) {
3899             /* Old ICC buglet */
3900             relation = RELATION_OBSERVING_STATIC;
3901         }
3902         newGameMode = IcsObserving;
3903         break;
3904       case RELATION_PLAYING_MYMOVE:
3905       case RELATION_PLAYING_NOTMYMOVE:
3906         newGameMode =
3907           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3908             IcsPlayingWhite : IcsPlayingBlack;
3909         break;
3910       case RELATION_EXAMINING:
3911         newGameMode = IcsExamining;
3912         break;
3913       case RELATION_ISOLATED_BOARD:
3914       default:
3915         /* Just display this board.  If user was doing something else,
3916            we will forget about it until the next board comes. */
3917         newGameMode = IcsIdle;
3918         break;
3919       case RELATION_STARTING_POSITION:
3920         newGameMode = gameMode;
3921         break;
3922     }
3923
3924     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3925          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3926       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3927       char *toSqr;
3928       for (k = 0; k < ranks; k++) {
3929         for (j = 0; j < files; j++)
3930           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3931         if(gameInfo.holdingsWidth > 1) {
3932              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3933              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3934         }
3935       }
3936       CopyBoard(partnerBoard, board);
3937       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3938         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3939         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3940       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3941       if(toSqr = strchr(str, '-')) {
3942         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3943         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3944       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3945       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3946       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3947       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3948       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3949       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3950                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3951       DisplayMessage(partnerStatus, "");
3952         partnerBoardValid = TRUE;
3953       return;
3954     }
3955
3956     /* Modify behavior for initial board display on move listing
3957        of wild games.
3958        */
3959     switch (ics_getting_history) {
3960       case H_FALSE:
3961       case H_REQUESTED:
3962         break;
3963       case H_GOT_REQ_HEADER:
3964       case H_GOT_UNREQ_HEADER:
3965         /* This is the initial position of the current game */
3966         gamenum = ics_gamenum;
3967         moveNum = 0;            /* old ICS bug workaround */
3968         if (to_play == 'B') {
3969           startedFromSetupPosition = TRUE;
3970           blackPlaysFirst = TRUE;
3971           moveNum = 1;
3972           if (forwardMostMove == 0) forwardMostMove = 1;
3973           if (backwardMostMove == 0) backwardMostMove = 1;
3974           if (currentMove == 0) currentMove = 1;
3975         }
3976         newGameMode = gameMode;
3977         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3978         break;
3979       case H_GOT_UNWANTED_HEADER:
3980         /* This is an initial board that we don't want */
3981         return;
3982       case H_GETTING_MOVES:
3983         /* Should not happen */
3984         DisplayError(_("Error gathering move list: extra board"), 0);
3985         ics_getting_history = H_FALSE;
3986         return;
3987     }
3988
3989    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3990                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3991      /* [HGM] We seem to have switched variant unexpectedly
3992       * Try to guess new variant from board size
3993       */
3994           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3995           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3996           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3997           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3998           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3999           if(!weird) newVariant = VariantNormal;
4000           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4001           /* Get a move list just to see the header, which
4002              will tell us whether this is really bug or zh */
4003           if (ics_getting_history == H_FALSE) {
4004             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4005             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4006             SendToICS(str);
4007           }
4008     }
4009
4010     /* Take action if this is the first board of a new game, or of a
4011        different game than is currently being displayed.  */
4012     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4013         relation == RELATION_ISOLATED_BOARD) {
4014
4015         /* Forget the old game and get the history (if any) of the new one */
4016         if (gameMode != BeginningOfGame) {
4017           Reset(TRUE, TRUE);
4018         }
4019         newGame = TRUE;
4020         if (appData.autoRaiseBoard) BoardToTop();
4021         prevMove = -3;
4022         if (gamenum == -1) {
4023             newGameMode = IcsIdle;
4024         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4025                    appData.getMoveList && !reqFlag) {
4026             /* Need to get game history */
4027             ics_getting_history = H_REQUESTED;
4028             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4029             SendToICS(str);
4030         }
4031
4032         /* Initially flip the board to have black on the bottom if playing
4033            black or if the ICS flip flag is set, but let the user change
4034            it with the Flip View button. */
4035         flipView = appData.autoFlipView ?
4036           (newGameMode == IcsPlayingBlack) || ics_flip :
4037           appData.flipView;
4038
4039         /* Done with values from previous mode; copy in new ones */
4040         gameMode = newGameMode;
4041         ModeHighlight();
4042         ics_gamenum = gamenum;
4043         if (gamenum == gs_gamenum) {
4044             int klen = strlen(gs_kind);
4045             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4046             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4047             gameInfo.event = StrSave(str);
4048         } else {
4049             gameInfo.event = StrSave("ICS game");
4050         }
4051         gameInfo.site = StrSave(appData.icsHost);
4052         gameInfo.date = PGNDate();
4053         gameInfo.round = StrSave("-");
4054         gameInfo.white = StrSave(white);
4055         gameInfo.black = StrSave(black);
4056         timeControl = basetime * 60 * 1000;
4057         timeControl_2 = 0;
4058         timeIncrement = increment * 1000;
4059         movesPerSession = 0;
4060         gameInfo.timeControl = TimeControlTagValue();
4061         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4062   if (appData.debugMode) {
4063     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4064     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4065     setbuf(debugFP, NULL);
4066   }
4067
4068         gameInfo.outOfBook = NULL;
4069
4070         /* Do we have the ratings? */
4071         if (strcmp(player1Name, white) == 0 &&
4072             strcmp(player2Name, black) == 0) {
4073             if (appData.debugMode)
4074               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4075                       player1Rating, player2Rating);
4076             gameInfo.whiteRating = player1Rating;
4077             gameInfo.blackRating = player2Rating;
4078         } else if (strcmp(player2Name, white) == 0 &&
4079                    strcmp(player1Name, black) == 0) {
4080             if (appData.debugMode)
4081               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4082                       player2Rating, player1Rating);
4083             gameInfo.whiteRating = player2Rating;
4084             gameInfo.blackRating = player1Rating;
4085         }
4086         player1Name[0] = player2Name[0] = NULLCHAR;
4087
4088         /* Silence shouts if requested */
4089         if (appData.quietPlay &&
4090             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4091             SendToICS(ics_prefix);
4092             SendToICS("set shout 0\n");
4093         }
4094     }
4095
4096     /* Deal with midgame name changes */
4097     if (!newGame) {
4098         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4099             if (gameInfo.white) free(gameInfo.white);
4100             gameInfo.white = StrSave(white);
4101         }
4102         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4103             if (gameInfo.black) free(gameInfo.black);
4104             gameInfo.black = StrSave(black);
4105         }
4106     }
4107
4108     /* Throw away game result if anything actually changes in examine mode */
4109     if (gameMode == IcsExamining && !newGame) {
4110         gameInfo.result = GameUnfinished;
4111         if (gameInfo.resultDetails != NULL) {
4112             free(gameInfo.resultDetails);
4113             gameInfo.resultDetails = NULL;
4114         }
4115     }
4116
4117     /* In pausing && IcsExamining mode, we ignore boards coming
4118        in if they are in a different variation than we are. */
4119     if (pauseExamInvalid) return;
4120     if (pausing && gameMode == IcsExamining) {
4121         if (moveNum <= pauseExamForwardMostMove) {
4122             pauseExamInvalid = TRUE;
4123             forwardMostMove = pauseExamForwardMostMove;
4124             return;
4125         }
4126     }
4127
4128   if (appData.debugMode) {
4129     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4130   }
4131     /* Parse the board */
4132     for (k = 0; k < ranks; k++) {
4133       for (j = 0; j < files; j++)
4134         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4135       if(gameInfo.holdingsWidth > 1) {
4136            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4137            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4138       }
4139     }
4140     CopyBoard(boards[moveNum], board);
4141     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4142     if (moveNum == 0) {
4143         startedFromSetupPosition =
4144           !CompareBoards(board, initialPosition);
4145         if(startedFromSetupPosition)
4146             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4147     }
4148
4149     /* [HGM] Set castling rights. Take the outermost Rooks,
4150        to make it also work for FRC opening positions. Note that board12
4151        is really defective for later FRC positions, as it has no way to
4152        indicate which Rook can castle if they are on the same side of King.
4153        For the initial position we grant rights to the outermost Rooks,
4154        and remember thos rights, and we then copy them on positions
4155        later in an FRC game. This means WB might not recognize castlings with
4156        Rooks that have moved back to their original position as illegal,
4157        but in ICS mode that is not its job anyway.
4158     */
4159     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4160     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4161
4162         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4163             if(board[0][i] == WhiteRook) j = i;
4164         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4165         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4166             if(board[0][i] == WhiteRook) j = i;
4167         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4168         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4169             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4170         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4171         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4172             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4173         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4174
4175         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4176         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4177             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4178         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4179             if(board[BOARD_HEIGHT-1][k] == bKing)
4180                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4181         if(gameInfo.variant == VariantTwoKings) {
4182             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4183             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4184             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4185         }
4186     } else { int r;
4187         r = boards[moveNum][CASTLING][0] = initialRights[0];
4188         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4189         r = boards[moveNum][CASTLING][1] = initialRights[1];
4190         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4191         r = boards[moveNum][CASTLING][3] = initialRights[3];
4192         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4193         r = boards[moveNum][CASTLING][4] = initialRights[4];
4194         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4195         /* wildcastle kludge: always assume King has rights */
4196         r = boards[moveNum][CASTLING][2] = initialRights[2];
4197         r = boards[moveNum][CASTLING][5] = initialRights[5];
4198     }
4199     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4200     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4201
4202
4203     if (ics_getting_history == H_GOT_REQ_HEADER ||
4204         ics_getting_history == H_GOT_UNREQ_HEADER) {
4205         /* This was an initial position from a move list, not
4206            the current position */
4207         return;
4208     }
4209
4210     /* Update currentMove and known move number limits */
4211     newMove = newGame || moveNum > forwardMostMove;
4212
4213     if (newGame) {
4214         forwardMostMove = backwardMostMove = currentMove = moveNum;
4215         if (gameMode == IcsExamining && moveNum == 0) {
4216           /* Workaround for ICS limitation: we are not told the wild
4217              type when starting to examine a game.  But if we ask for
4218              the move list, the move list header will tell us */
4219             ics_getting_history = H_REQUESTED;
4220             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4221             SendToICS(str);
4222         }
4223     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4224                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4225 #if ZIPPY
4226         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4227         /* [HGM] applied this also to an engine that is silently watching        */
4228         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4229             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4230             gameInfo.variant == currentlyInitializedVariant) {
4231           takeback = forwardMostMove - moveNum;
4232           for (i = 0; i < takeback; i++) {
4233             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4234             SendToProgram("undo\n", &first);
4235           }
4236         }
4237 #endif
4238
4239         forwardMostMove = moveNum;
4240         if (!pausing || currentMove > forwardMostMove)
4241           currentMove = forwardMostMove;
4242     } else {
4243         /* New part of history that is not contiguous with old part */
4244         if (pausing && gameMode == IcsExamining) {
4245             pauseExamInvalid = TRUE;
4246             forwardMostMove = pauseExamForwardMostMove;
4247             return;
4248         }
4249         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4250 #if ZIPPY
4251             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4252                 // [HGM] when we will receive the move list we now request, it will be
4253                 // fed to the engine from the first move on. So if the engine is not
4254                 // in the initial position now, bring it there.
4255                 InitChessProgram(&first, 0);
4256             }
4257 #endif
4258             ics_getting_history = H_REQUESTED;
4259             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4260             SendToICS(str);
4261         }
4262         forwardMostMove = backwardMostMove = currentMove = moveNum;
4263     }
4264
4265     /* Update the clocks */
4266     if (strchr(elapsed_time, '.')) {
4267       /* Time is in ms */
4268       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4269       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4270     } else {
4271       /* Time is in seconds */
4272       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4273       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4274     }
4275
4276
4277 #if ZIPPY
4278     if (appData.zippyPlay && newGame &&
4279         gameMode != IcsObserving && gameMode != IcsIdle &&
4280         gameMode != IcsExamining)
4281       ZippyFirstBoard(moveNum, basetime, increment);
4282 #endif
4283
4284     /* Put the move on the move list, first converting
4285        to canonical algebraic form. */
4286     if (moveNum > 0) {
4287   if (appData.debugMode) {
4288     if (appData.debugMode) { int f = forwardMostMove;
4289         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4290                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4291                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4292     }
4293     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4294     fprintf(debugFP, "moveNum = %d\n", moveNum);
4295     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4296     setbuf(debugFP, NULL);
4297   }
4298         if (moveNum <= backwardMostMove) {
4299             /* We don't know what the board looked like before
4300                this move.  Punt. */
4301           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4302             strcat(parseList[moveNum - 1], " ");
4303             strcat(parseList[moveNum - 1], elapsed_time);
4304             moveList[moveNum - 1][0] = NULLCHAR;
4305         } else if (strcmp(move_str, "none") == 0) {
4306             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4307             /* Again, we don't know what the board looked like;
4308                this is really the start of the game. */
4309             parseList[moveNum - 1][0] = NULLCHAR;
4310             moveList[moveNum - 1][0] = NULLCHAR;
4311             backwardMostMove = moveNum;
4312             startedFromSetupPosition = TRUE;
4313             fromX = fromY = toX = toY = -1;
4314         } else {
4315           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4316           //                 So we parse the long-algebraic move string in stead of the SAN move
4317           int valid; char buf[MSG_SIZ], *prom;
4318
4319           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4320                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4321           // str looks something like "Q/a1-a2"; kill the slash
4322           if(str[1] == '/')
4323             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4324           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4325           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4326                 strcat(buf, prom); // long move lacks promo specification!
4327           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4328                 if(appData.debugMode)
4329                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4330                 safeStrCpy(move_str, buf, MSG_SIZ);
4331           }
4332           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4333                                 &fromX, &fromY, &toX, &toY, &promoChar)
4334                || ParseOneMove(buf, moveNum - 1, &moveType,
4335                                 &fromX, &fromY, &toX, &toY, &promoChar);
4336           // end of long SAN patch
4337           if (valid) {
4338             (void) CoordsToAlgebraic(boards[moveNum - 1],
4339                                      PosFlags(moveNum - 1),
4340                                      fromY, fromX, toY, toX, promoChar,
4341                                      parseList[moveNum-1]);
4342             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4343               case MT_NONE:
4344               case MT_STALEMATE:
4345               default:
4346                 break;
4347               case MT_CHECK:
4348                 if(gameInfo.variant != VariantShogi)
4349                     strcat(parseList[moveNum - 1], "+");
4350                 break;
4351               case MT_CHECKMATE:
4352               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4353                 strcat(parseList[moveNum - 1], "#");
4354                 break;
4355             }
4356             strcat(parseList[moveNum - 1], " ");
4357             strcat(parseList[moveNum - 1], elapsed_time);
4358             /* currentMoveString is set as a side-effect of ParseOneMove */
4359             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4360             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4361             strcat(moveList[moveNum - 1], "\n");
4362
4363             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4364               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4365                 ChessSquare old, new = boards[moveNum][k][j];
4366                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4367                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4368                   if(old == new) continue;
4369                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4370                   else if(new == WhiteWazir || new == BlackWazir) {
4371                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4372                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4373                       else boards[moveNum][k][j] = old; // preserve type of Gold
4374                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4375                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4376               }
4377           } else {
4378             /* Move from ICS was illegal!?  Punt. */
4379             if (appData.debugMode) {
4380               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4381               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4382             }
4383             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4384             strcat(parseList[moveNum - 1], " ");
4385             strcat(parseList[moveNum - 1], elapsed_time);
4386             moveList[moveNum - 1][0] = NULLCHAR;
4387             fromX = fromY = toX = toY = -1;
4388           }
4389         }
4390   if (appData.debugMode) {
4391     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4392     setbuf(debugFP, NULL);
4393   }
4394
4395 #if ZIPPY
4396         /* Send move to chess program (BEFORE animating it). */
4397         if (appData.zippyPlay && !newGame && newMove &&
4398            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4399
4400             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4401                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4402                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4403                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4404                             move_str);
4405                     DisplayError(str, 0);
4406                 } else {
4407                     if (first.sendTime) {
4408                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4409                     }
4410                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4411                     if (firstMove && !bookHit) {
4412                         firstMove = FALSE;
4413                         if (first.useColors) {
4414                           SendToProgram(gameMode == IcsPlayingWhite ?
4415                                         "white\ngo\n" :
4416                                         "black\ngo\n", &first);
4417                         } else {
4418                           SendToProgram("go\n", &first);
4419                         }
4420                         first.maybeThinking = TRUE;
4421                     }
4422                 }
4423             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4424               if (moveList[moveNum - 1][0] == NULLCHAR) {
4425                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4426                 DisplayError(str, 0);
4427               } else {
4428                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4429                 SendMoveToProgram(moveNum - 1, &first);
4430               }
4431             }
4432         }
4433 #endif
4434     }
4435
4436     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4437         /* If move comes from a remote source, animate it.  If it
4438            isn't remote, it will have already been animated. */
4439         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4440             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4441         }
4442         if (!pausing && appData.highlightLastMove) {
4443             SetHighlights(fromX, fromY, toX, toY);
4444         }
4445     }
4446
4447     /* Start the clocks */
4448     whiteFlag = blackFlag = FALSE;
4449     appData.clockMode = !(basetime == 0 && increment == 0);
4450     if (ticking == 0) {
4451       ics_clock_paused = TRUE;
4452       StopClocks();
4453     } else if (ticking == 1) {
4454       ics_clock_paused = FALSE;
4455     }
4456     if (gameMode == IcsIdle ||
4457         relation == RELATION_OBSERVING_STATIC ||
4458         relation == RELATION_EXAMINING ||
4459         ics_clock_paused)
4460       DisplayBothClocks();
4461     else
4462       StartClocks();
4463
4464     /* Display opponents and material strengths */
4465     if (gameInfo.variant != VariantBughouse &&
4466         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4467         if (tinyLayout || smallLayout) {
4468             if(gameInfo.variant == VariantNormal)
4469               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4470                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4471                     basetime, increment);
4472             else
4473               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4474                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4475                     basetime, increment, (int) gameInfo.variant);
4476         } else {
4477             if(gameInfo.variant == VariantNormal)
4478               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4479                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4480                     basetime, increment);
4481             else
4482               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4483                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4484                     basetime, increment, VariantName(gameInfo.variant));
4485         }
4486         DisplayTitle(str);
4487   if (appData.debugMode) {
4488     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4489   }
4490     }
4491
4492
4493     /* Display the board */
4494     if (!pausing && !appData.noGUI) {
4495
4496       if (appData.premove)
4497           if (!gotPremove ||
4498              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4499              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4500               ClearPremoveHighlights();
4501
4502       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4503         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4504       DrawPosition(j, boards[currentMove]);
4505
4506       DisplayMove(moveNum - 1);
4507       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4508             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4509               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4510         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4511       }
4512     }
4513
4514     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4515 #if ZIPPY
4516     if(bookHit) { // [HGM] book: simulate book reply
4517         static char bookMove[MSG_SIZ]; // a bit generous?
4518
4519         programStats.nodes = programStats.depth = programStats.time =
4520         programStats.score = programStats.got_only_move = 0;
4521         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4522
4523         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4524         strcat(bookMove, bookHit);
4525         HandleMachineMove(bookMove, &first);
4526     }
4527 #endif
4528 }
4529
4530 void
4531 GetMoveListEvent()
4532 {
4533     char buf[MSG_SIZ];
4534     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4535         ics_getting_history = H_REQUESTED;
4536         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4537         SendToICS(buf);
4538     }
4539 }
4540
4541 void
4542 AnalysisPeriodicEvent(force)
4543      int force;
4544 {
4545     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4546          && !force) || !appData.periodicUpdates)
4547       return;
4548
4549     /* Send . command to Crafty to collect stats */
4550     SendToProgram(".\n", &first);
4551
4552     /* Don't send another until we get a response (this makes
4553        us stop sending to old Crafty's which don't understand
4554        the "." command (sending illegal cmds resets node count & time,
4555        which looks bad)) */
4556     programStats.ok_to_send = 0;
4557 }
4558
4559 void ics_update_width(new_width)
4560         int new_width;
4561 {
4562         ics_printf("set width %d\n", new_width);
4563 }
4564
4565 void
4566 SendMoveToProgram(moveNum, cps)
4567      int moveNum;
4568      ChessProgramState *cps;
4569 {
4570     char buf[MSG_SIZ];
4571
4572     if (cps->useUsermove) {
4573       SendToProgram("usermove ", cps);
4574     }
4575     if (cps->useSAN) {
4576       char *space;
4577       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4578         int len = space - parseList[moveNum];
4579         memcpy(buf, parseList[moveNum], len);
4580         buf[len++] = '\n';
4581         buf[len] = NULLCHAR;
4582       } else {
4583         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4584       }
4585       SendToProgram(buf, cps);
4586     } else {
4587       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4588         AlphaRank(moveList[moveNum], 4);
4589         SendToProgram(moveList[moveNum], cps);
4590         AlphaRank(moveList[moveNum], 4); // and back
4591       } else
4592       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4593        * the engine. It would be nice to have a better way to identify castle
4594        * moves here. */
4595       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4596                                                                          && cps->useOOCastle) {
4597         int fromX = moveList[moveNum][0] - AAA;
4598         int fromY = moveList[moveNum][1] - ONE;
4599         int toX = moveList[moveNum][2] - AAA;
4600         int toY = moveList[moveNum][3] - ONE;
4601         if((boards[moveNum][fromY][fromX] == WhiteKing
4602             && boards[moveNum][toY][toX] == WhiteRook)
4603            || (boards[moveNum][fromY][fromX] == BlackKing
4604                && boards[moveNum][toY][toX] == BlackRook)) {
4605           if(toX > fromX) SendToProgram("O-O\n", cps);
4606           else SendToProgram("O-O-O\n", cps);
4607         }
4608         else SendToProgram(moveList[moveNum], cps);
4609       }
4610       else SendToProgram(moveList[moveNum], cps);
4611       /* End of additions by Tord */
4612     }
4613
4614     /* [HGM] setting up the opening has brought engine in force mode! */
4615     /*       Send 'go' if we are in a mode where machine should play. */
4616     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4617         (gameMode == TwoMachinesPlay   ||
4618 #if ZIPPY
4619          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4620 #endif
4621          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4622         SendToProgram("go\n", cps);
4623   if (appData.debugMode) {
4624     fprintf(debugFP, "(extra)\n");
4625   }
4626     }
4627     setboardSpoiledMachineBlack = 0;
4628 }
4629
4630 void
4631 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4632      ChessMove moveType;
4633      int fromX, fromY, toX, toY;
4634      char promoChar;
4635 {
4636     char user_move[MSG_SIZ];
4637
4638     switch (moveType) {
4639       default:
4640         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4641                 (int)moveType, fromX, fromY, toX, toY);
4642         DisplayError(user_move + strlen("say "), 0);
4643         break;
4644       case WhiteKingSideCastle:
4645       case BlackKingSideCastle:
4646       case WhiteQueenSideCastleWild:
4647       case BlackQueenSideCastleWild:
4648       /* PUSH Fabien */
4649       case WhiteHSideCastleFR:
4650       case BlackHSideCastleFR:
4651       /* POP Fabien */
4652         snprintf(user_move, MSG_SIZ, "o-o\n");
4653         break;
4654       case WhiteQueenSideCastle:
4655       case BlackQueenSideCastle:
4656       case WhiteKingSideCastleWild:
4657       case BlackKingSideCastleWild:
4658       /* PUSH Fabien */
4659       case WhiteASideCastleFR:
4660       case BlackASideCastleFR:
4661       /* POP Fabien */
4662         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4663         break;
4664       case WhiteNonPromotion:
4665       case BlackNonPromotion:
4666         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4667         break;
4668       case WhitePromotion:
4669       case BlackPromotion:
4670         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4671           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4672                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4673                 PieceToChar(WhiteFerz));
4674         else if(gameInfo.variant == VariantGreat)
4675           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4676                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4677                 PieceToChar(WhiteMan));
4678         else
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 promoChar);
4682         break;
4683       case WhiteDrop:
4684       case BlackDrop:
4685       drop:
4686         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4687                  ToUpper(PieceToChar((ChessSquare) fromX)),
4688                  AAA + toX, ONE + toY);
4689         break;
4690       case IllegalMove:  /* could be a variant we don't quite understand */
4691         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4692       case NormalMove:
4693       case WhiteCapturesEnPassant:
4694       case BlackCapturesEnPassant:
4695         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4696                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4697         break;
4698     }
4699     SendToICS(user_move);
4700     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4701         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4702 }
4703
4704 void
4705 UploadGameEvent()
4706 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4707     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4708     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4709     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4710         DisplayError("You cannot do this while you are playing or observing", 0);
4711         return;
4712     }
4713     if(gameMode != IcsExamining) { // is this ever not the case?
4714         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4715
4716         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4717           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4718         } else { // on FICS we must first go to general examine mode
4719           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4720         }
4721         if(gameInfo.variant != VariantNormal) {
4722             // try figure out wild number, as xboard names are not always valid on ICS
4723             for(i=1; i<=36; i++) {
4724               snprintf(buf, MSG_SIZ, "wild/%d", i);
4725                 if(StringToVariant(buf) == gameInfo.variant) break;
4726             }
4727             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4728             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4729             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4730         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4731         SendToICS(ics_prefix);
4732         SendToICS(buf);
4733         if(startedFromSetupPosition || backwardMostMove != 0) {
4734           fen = PositionToFEN(backwardMostMove, NULL);
4735           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4736             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4737             SendToICS(buf);
4738           } else { // FICS: everything has to set by separate bsetup commands
4739             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4740             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4741             SendToICS(buf);
4742             if(!WhiteOnMove(backwardMostMove)) {
4743                 SendToICS("bsetup tomove black\n");
4744             }
4745             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4746             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4747             SendToICS(buf);
4748             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4749             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4750             SendToICS(buf);
4751             i = boards[backwardMostMove][EP_STATUS];
4752             if(i >= 0) { // set e.p.
4753               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4754                 SendToICS(buf);
4755             }
4756             bsetup++;
4757           }
4758         }
4759       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4760             SendToICS("bsetup done\n"); // switch to normal examining.
4761     }
4762     for(i = backwardMostMove; i<last; i++) {
4763         char buf[20];
4764         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4765         SendToICS(buf);
4766     }
4767     SendToICS(ics_prefix);
4768     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4769 }
4770
4771 void
4772 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4773      int rf, ff, rt, ft;
4774      char promoChar;
4775      char move[7];
4776 {
4777     if (rf == DROP_RANK) {
4778       sprintf(move, "%c@%c%c\n",
4779                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4780     } else {
4781         if (promoChar == 'x' || promoChar == NULLCHAR) {
4782           sprintf(move, "%c%c%c%c\n",
4783                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4784         } else {
4785             sprintf(move, "%c%c%c%c%c\n",
4786                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4787         }
4788     }
4789 }
4790
4791 void
4792 ProcessICSInitScript(f)
4793      FILE *f;
4794 {
4795     char buf[MSG_SIZ];
4796
4797     while (fgets(buf, MSG_SIZ, f)) {
4798         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4799     }
4800
4801     fclose(f);
4802 }
4803
4804
4805 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4806 void
4807 AlphaRank(char *move, int n)
4808 {
4809 //    char *p = move, c; int x, y;
4810
4811     if (appData.debugMode) {
4812         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4813     }
4814
4815     if(move[1]=='*' &&
4816        move[2]>='0' && move[2]<='9' &&
4817        move[3]>='a' && move[3]<='x'    ) {
4818         move[1] = '@';
4819         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4820         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4821     } else
4822     if(move[0]>='0' && move[0]<='9' &&
4823        move[1]>='a' && move[1]<='x' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         /* input move, Shogi -> normal */
4827         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4828         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4829         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4830         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4831     } else
4832     if(move[1]=='@' &&
4833        move[3]>='0' && move[3]<='9' &&
4834        move[2]>='a' && move[2]<='x'    ) {
4835         move[1] = '*';
4836         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4837         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4838     } else
4839     if(
4840        move[0]>='a' && move[0]<='x' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843          /* output move, normal -> Shogi */
4844         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4845         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4846         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4847         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4848         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4849     }
4850     if (appData.debugMode) {
4851         fprintf(debugFP, "   out = '%s'\n", move);
4852     }
4853 }
4854
4855 char yy_textstr[8000];
4856
4857 /* Parser for moves from gnuchess, ICS, or user typein box */
4858 Boolean
4859 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4860      char *move;
4861      int moveNum;
4862      ChessMove *moveType;
4863      int *fromX, *fromY, *toX, *toY;
4864      char *promoChar;
4865 {
4866     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4867
4868     switch (*moveType) {
4869       case WhitePromotion:
4870       case BlackPromotion:
4871       case WhiteNonPromotion:
4872       case BlackNonPromotion:
4873       case NormalMove:
4874       case WhiteCapturesEnPassant:
4875       case BlackCapturesEnPassant:
4876       case WhiteKingSideCastle:
4877       case WhiteQueenSideCastle:
4878       case BlackKingSideCastle:
4879       case BlackQueenSideCastle:
4880       case WhiteKingSideCastleWild:
4881       case WhiteQueenSideCastleWild:
4882       case BlackKingSideCastleWild:
4883       case BlackQueenSideCastleWild:
4884       /* Code added by Tord: */
4885       case WhiteHSideCastleFR:
4886       case WhiteASideCastleFR:
4887       case BlackHSideCastleFR:
4888       case BlackASideCastleFR:
4889       /* End of code added by Tord */
4890       case IllegalMove:         /* bug or odd chess variant */
4891         *fromX = currentMoveString[0] - AAA;
4892         *fromY = currentMoveString[1] - ONE;
4893         *toX = currentMoveString[2] - AAA;
4894         *toY = currentMoveString[3] - ONE;
4895         *promoChar = currentMoveString[4];
4896         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4897             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4898     if (appData.debugMode) {
4899         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4900     }
4901             *fromX = *fromY = *toX = *toY = 0;
4902             return FALSE;
4903         }
4904         if (appData.testLegality) {
4905           return (*moveType != IllegalMove);
4906         } else {
4907           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4908                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4909         }
4910
4911       case WhiteDrop:
4912       case BlackDrop:
4913         *fromX = *moveType == WhiteDrop ?
4914           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4915           (int) CharToPiece(ToLower(currentMoveString[0]));
4916         *fromY = DROP_RANK;
4917         *toX = currentMoveString[2] - AAA;
4918         *toY = currentMoveString[3] - ONE;
4919         *promoChar = NULLCHAR;
4920         return TRUE;
4921
4922       case AmbiguousMove:
4923       case ImpossibleMove:
4924       case EndOfFile:
4925       case ElapsedTime:
4926       case Comment:
4927       case PGNTag:
4928       case NAG:
4929       case WhiteWins:
4930       case BlackWins:
4931       case GameIsDrawn:
4932       default:
4933     if (appData.debugMode) {
4934         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4935     }
4936         /* bug? */
4937         *fromX = *fromY = *toX = *toY = 0;
4938         *promoChar = NULLCHAR;
4939         return FALSE;
4940     }
4941 }
4942
4943
4944 void
4945 ParsePV(char *pv, Boolean storeComments)
4946 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4947   int fromX, fromY, toX, toY; char promoChar;
4948   ChessMove moveType;
4949   Boolean valid;
4950   int nr = 0;
4951
4952   endPV = forwardMostMove;
4953   do {
4954     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4955     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4956     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4957 if(appData.debugMode){
4958 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4959 }
4960     if(!valid && nr == 0 &&
4961        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4962         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4963         // Hande case where played move is different from leading PV move
4964         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4965         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4966         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4967         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4968           endPV += 2; // if position different, keep this
4969           moveList[endPV-1][0] = fromX + AAA;
4970           moveList[endPV-1][1] = fromY + ONE;
4971           moveList[endPV-1][2] = toX + AAA;
4972           moveList[endPV-1][3] = toY + ONE;
4973           parseList[endPV-1][0] = NULLCHAR;
4974           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4975         }
4976       }
4977     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4978     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4979     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4980     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4981         valid++; // allow comments in PV
4982         continue;
4983     }
4984     nr++;
4985     if(endPV+1 > framePtr) break; // no space, truncate
4986     if(!valid) break;
4987     endPV++;
4988     CopyBoard(boards[endPV], boards[endPV-1]);
4989     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4990     moveList[endPV-1][0] = fromX + AAA;
4991     moveList[endPV-1][1] = fromY + ONE;
4992     moveList[endPV-1][2] = toX + AAA;
4993     moveList[endPV-1][3] = toY + ONE;
4994     moveList[endPV-1][4] = promoChar;
4995     moveList[endPV-1][5] = NULLCHAR;
4996     strncat(moveList[endPV-1], "\n", MOVE_LEN);
4997     if(storeComments)
4998         CoordsToAlgebraic(boards[endPV - 1],
4999                              PosFlags(endPV - 1),
5000                              fromY, fromX, toY, toX, promoChar,
5001                              parseList[endPV - 1]);
5002     else
5003         parseList[endPV-1][0] = NULLCHAR;
5004   } while(valid);
5005   currentMove = endPV;
5006   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5007   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5008                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5009   DrawPosition(TRUE, boards[currentMove]);
5010 }
5011
5012 static int lastX, lastY;
5013
5014 Boolean
5015 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5016 {
5017         int startPV;
5018         char *p;
5019
5020         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5021         lastX = x; lastY = y;
5022         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5023         startPV = index;
5024         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5025         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5026         index = startPV;
5027         do{ while(buf[index] && buf[index] != '\n') index++;
5028         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5029         buf[index] = 0;
5030         ParsePV(buf+startPV, FALSE);
5031         *start = startPV; *end = index-1;
5032         return TRUE;
5033 }
5034
5035 Boolean
5036 LoadPV(int x, int y)
5037 { // called on right mouse click to load PV
5038   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5039   lastX = x; lastY = y;
5040   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5041   return TRUE;
5042 }
5043
5044 void
5045 UnLoadPV()
5046 {
5047   if(endPV < 0) return;
5048   endPV = -1;
5049   currentMove = forwardMostMove;
5050   ClearPremoveHighlights();
5051   DrawPosition(TRUE, boards[currentMove]);
5052 }
5053
5054 void
5055 MovePV(int x, int y, int h)
5056 { // step through PV based on mouse coordinates (called on mouse move)
5057   int margin = h>>3, step = 0;
5058
5059   if(endPV < 0) return;
5060   // we must somehow check if right button is still down (might be released off board!)
5061   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5062   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5063   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5064   if(!step) return;
5065   lastX = x; lastY = y;
5066   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5067   currentMove += step;
5068   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5069   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5070                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5071   DrawPosition(FALSE, boards[currentMove]);
5072 }
5073
5074
5075 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5076 // All positions will have equal probability, but the current method will not provide a unique
5077 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5078 #define DARK 1
5079 #define LITE 2
5080 #define ANY 3
5081
5082 int squaresLeft[4];
5083 int piecesLeft[(int)BlackPawn];
5084 int seed, nrOfShuffles;
5085
5086 void GetPositionNumber()
5087 {       // sets global variable seed
5088         int i;
5089
5090         seed = appData.defaultFrcPosition;
5091         if(seed < 0) { // randomize based on time for negative FRC position numbers
5092                 for(i=0; i<50; i++) seed += random();
5093                 seed = random() ^ random() >> 8 ^ random() << 8;
5094                 if(seed<0) seed = -seed;
5095         }
5096 }
5097
5098 int put(Board board, int pieceType, int rank, int n, int shade)
5099 // put the piece on the (n-1)-th empty squares of the given shade
5100 {
5101         int i;
5102
5103         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5104                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5105                         board[rank][i] = (ChessSquare) pieceType;
5106                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5107                         squaresLeft[ANY]--;
5108                         piecesLeft[pieceType]--;
5109                         return i;
5110                 }
5111         }
5112         return -1;
5113 }
5114
5115
5116 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5117 // calculate where the next piece goes, (any empty square), and put it there
5118 {
5119         int i;
5120
5121         i = seed % squaresLeft[shade];
5122         nrOfShuffles *= squaresLeft[shade];
5123         seed /= squaresLeft[shade];
5124         put(board, pieceType, rank, i, shade);
5125 }
5126
5127 void AddTwoPieces(Board board, int pieceType, int rank)
5128 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5129 {
5130         int i, n=squaresLeft[ANY], j=n-1, k;
5131
5132         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5133         i = seed % k;  // pick one
5134         nrOfShuffles *= k;
5135         seed /= k;
5136         while(i >= j) i -= j--;
5137         j = n - 1 - j; i += j;
5138         put(board, pieceType, rank, j, ANY);
5139         put(board, pieceType, rank, i, ANY);
5140 }
5141
5142 void SetUpShuffle(Board board, int number)
5143 {
5144         int i, p, first=1;
5145
5146         GetPositionNumber(); nrOfShuffles = 1;
5147
5148         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5149         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5150         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5151
5152         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5153
5154         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5155             p = (int) board[0][i];
5156             if(p < (int) BlackPawn) piecesLeft[p] ++;
5157             board[0][i] = EmptySquare;
5158         }
5159
5160         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5161             // shuffles restricted to allow normal castling put KRR first
5162             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5163                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5164             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5165                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5166             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5167                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5168             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5169                 put(board, WhiteRook, 0, 0, ANY);
5170             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5171         }
5172
5173         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5174             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5175             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5176                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5177                 while(piecesLeft[p] >= 2) {
5178                     AddOnePiece(board, p, 0, LITE);
5179                     AddOnePiece(board, p, 0, DARK);
5180                 }
5181                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5182             }
5183
5184         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5185             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5186             // but we leave King and Rooks for last, to possibly obey FRC restriction
5187             if(p == (int)WhiteRook) continue;
5188             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5189             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5190         }
5191
5192         // now everything is placed, except perhaps King (Unicorn) and Rooks
5193
5194         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5195             // Last King gets castling rights
5196             while(piecesLeft[(int)WhiteUnicorn]) {
5197                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5198                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5199             }
5200
5201             while(piecesLeft[(int)WhiteKing]) {
5202                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5203                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5204             }
5205
5206
5207         } else {
5208             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5209             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5210         }
5211
5212         // Only Rooks can be left; simply place them all
5213         while(piecesLeft[(int)WhiteRook]) {
5214                 i = put(board, WhiteRook, 0, 0, ANY);
5215                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5216                         if(first) {
5217                                 first=0;
5218                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5219                         }
5220                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5221                 }
5222         }
5223         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5224             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5225         }
5226
5227         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5228 }
5229
5230 int SetCharTable( char *table, const char * map )
5231 /* [HGM] moved here from winboard.c because of its general usefulness */
5232 /*       Basically a safe strcpy that uses the last character as King */
5233 {
5234     int result = FALSE; int NrPieces;
5235
5236     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5237                     && NrPieces >= 12 && !(NrPieces&1)) {
5238         int i; /* [HGM] Accept even length from 12 to 34 */
5239
5240         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5241         for( i=0; i<NrPieces/2-1; i++ ) {
5242             table[i] = map[i];
5243             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5244         }
5245         table[(int) WhiteKing]  = map[NrPieces/2-1];
5246         table[(int) BlackKing]  = map[NrPieces-1];
5247
5248         result = TRUE;
5249     }
5250
5251     return result;
5252 }
5253
5254 void Prelude(Board board)
5255 {       // [HGM] superchess: random selection of exo-pieces
5256         int i, j, k; ChessSquare p;
5257         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5258
5259         GetPositionNumber(); // use FRC position number
5260
5261         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5262             SetCharTable(pieceToChar, appData.pieceToCharTable);
5263             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5264                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5265         }
5266
5267         j = seed%4;                 seed /= 4;
5268         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5269         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5270         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5271         j = seed%3 + (seed%3 >= j); seed /= 3;
5272         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5273         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5274         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5275         j = seed%3;                 seed /= 3;
5276         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%2 + (seed%2 >= j); seed /= 2;
5280         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5284         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5285         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5286         put(board, exoPieces[0],    0, 0, ANY);
5287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5288 }
5289
5290 void
5291 InitPosition(redraw)
5292      int redraw;
5293 {
5294     ChessSquare (* pieces)[BOARD_FILES];
5295     int i, j, pawnRow, overrule,
5296     oldx = gameInfo.boardWidth,
5297     oldy = gameInfo.boardHeight,
5298     oldh = gameInfo.holdingsWidth,
5299     oldv = gameInfo.variant;
5300
5301     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5302
5303     /* [AS] Initialize pv info list [HGM] and game status */
5304     {
5305         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5306             pvInfoList[i].depth = 0;
5307             boards[i][EP_STATUS] = EP_NONE;
5308             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5309         }
5310
5311         initialRulePlies = 0; /* 50-move counter start */
5312
5313         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5314         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5315     }
5316
5317
5318     /* [HGM] logic here is completely changed. In stead of full positions */
5319     /* the initialized data only consist of the two backranks. The switch */
5320     /* selects which one we will use, which is than copied to the Board   */
5321     /* initialPosition, which for the rest is initialized by Pawns and    */
5322     /* empty squares. This initial position is then copied to boards[0],  */
5323     /* possibly after shuffling, so that it remains available.            */
5324
5325     gameInfo.holdingsWidth = 0; /* default board sizes */
5326     gameInfo.boardWidth    = 8;
5327     gameInfo.boardHeight   = 8;
5328     gameInfo.holdingsSize  = 0;
5329     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5330     for(i=0; i<BOARD_FILES-2; i++)
5331       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5332     initialPosition[EP_STATUS] = EP_NONE;
5333     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5334     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5335          SetCharTable(pieceNickName, appData.pieceNickNames);
5336     else SetCharTable(pieceNickName, "............");
5337     pieces = FIDEArray;
5338
5339     switch (gameInfo.variant) {
5340     case VariantFischeRandom:
5341       shuffleOpenings = TRUE;
5342     default:
5343       break;
5344     case VariantShatranj:
5345       pieces = ShatranjArray;
5346       nrCastlingRights = 0;
5347       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5348       break;
5349     case VariantMakruk:
5350       pieces = makrukArray;
5351       nrCastlingRights = 0;
5352       startedFromSetupPosition = TRUE;
5353       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5354       break;
5355     case VariantTwoKings:
5356       pieces = twoKingsArray;
5357       break;
5358     case VariantCapaRandom:
5359       shuffleOpenings = TRUE;
5360     case VariantCapablanca:
5361       pieces = CapablancaArray;
5362       gameInfo.boardWidth = 10;
5363       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5364       break;
5365     case VariantGothic:
5366       pieces = GothicArray;
5367       gameInfo.boardWidth = 10;
5368       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5369       break;
5370     case VariantSChess:
5371       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5372       gameInfo.holdingsSize = 7;
5373       break;
5374     case VariantJanus:
5375       pieces = JanusArray;
5376       gameInfo.boardWidth = 10;
5377       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5378       nrCastlingRights = 6;
5379         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5380         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5381         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5382         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5383         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5384         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5385       break;
5386     case VariantFalcon:
5387       pieces = FalconArray;
5388       gameInfo.boardWidth = 10;
5389       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5390       break;
5391     case VariantXiangqi:
5392       pieces = XiangqiArray;
5393       gameInfo.boardWidth  = 9;
5394       gameInfo.boardHeight = 10;
5395       nrCastlingRights = 0;
5396       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5397       break;
5398     case VariantShogi:
5399       pieces = ShogiArray;
5400       gameInfo.boardWidth  = 9;
5401       gameInfo.boardHeight = 9;
5402       gameInfo.holdingsSize = 7;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5405       break;
5406     case VariantCourier:
5407       pieces = CourierArray;
5408       gameInfo.boardWidth  = 12;
5409       nrCastlingRights = 0;
5410       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5411       break;
5412     case VariantKnightmate:
5413       pieces = KnightmateArray;
5414       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5415       break;
5416     case VariantFairy:
5417       pieces = fairyArray;
5418       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5419       break;
5420     case VariantGreat:
5421       pieces = GreatArray;
5422       gameInfo.boardWidth = 10;
5423       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5424       gameInfo.holdingsSize = 8;
5425       break;
5426     case VariantSuper:
5427       pieces = FIDEArray;
5428       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5429       gameInfo.holdingsSize = 8;
5430       startedFromSetupPosition = TRUE;
5431       break;
5432     case VariantCrazyhouse:
5433     case VariantBughouse:
5434       pieces = FIDEArray;
5435       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5436       gameInfo.holdingsSize = 5;
5437       break;
5438     case VariantWildCastle:
5439       pieces = FIDEArray;
5440       /* !!?shuffle with kings guaranteed to be on d or e file */
5441       shuffleOpenings = 1;
5442       break;
5443     case VariantNoCastle:
5444       pieces = FIDEArray;
5445       nrCastlingRights = 0;
5446       /* !!?unconstrained back-rank shuffle */
5447       shuffleOpenings = 1;
5448       break;
5449     }
5450
5451     overrule = 0;
5452     if(appData.NrFiles >= 0) {
5453         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5454         gameInfo.boardWidth = appData.NrFiles;
5455     }
5456     if(appData.NrRanks >= 0) {
5457         gameInfo.boardHeight = appData.NrRanks;
5458     }
5459     if(appData.holdingsSize >= 0) {
5460         i = appData.holdingsSize;
5461         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5462         gameInfo.holdingsSize = i;
5463     }
5464     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5465     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5466         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5467
5468     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5469     if(pawnRow < 1) pawnRow = 1;
5470     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5471
5472     /* User pieceToChar list overrules defaults */
5473     if(appData.pieceToCharTable != NULL)
5474         SetCharTable(pieceToChar, appData.pieceToCharTable);
5475
5476     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5477
5478         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5479             s = (ChessSquare) 0; /* account holding counts in guard band */
5480         for( i=0; i<BOARD_HEIGHT; i++ )
5481             initialPosition[i][j] = s;
5482
5483         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5484         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5485         initialPosition[pawnRow][j] = WhitePawn;
5486         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5487         if(gameInfo.variant == VariantXiangqi) {
5488             if(j&1) {
5489                 initialPosition[pawnRow][j] =
5490                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5491                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5492                    initialPosition[2][j] = WhiteCannon;
5493                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5494                 }
5495             }
5496         }
5497         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5498     }
5499     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5500
5501             j=BOARD_LEFT+1;
5502             initialPosition[1][j] = WhiteBishop;
5503             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5504             j=BOARD_RGHT-2;
5505             initialPosition[1][j] = WhiteRook;
5506             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5507     }
5508
5509     if( nrCastlingRights == -1) {
5510         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5511         /*       This sets default castling rights from none to normal corners   */
5512         /* Variants with other castling rights must set them themselves above    */
5513         nrCastlingRights = 6;
5514
5515         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5516         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5517         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5518         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5519         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5520         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5521      }
5522
5523      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5524      if(gameInfo.variant == VariantGreat) { // promotion commoners
5525         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5526         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5527         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5528         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5529      }
5530      if( gameInfo.variant == VariantSChess ) {
5531       initialPosition[1][0] = BlackMarshall;
5532       initialPosition[2][0] = BlackAngel;
5533       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5534       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5535       initialPosition[1][1] = initialPosition[2][1] = 
5536       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5537      }
5538   if (appData.debugMode) {
5539     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5540   }
5541     if(shuffleOpenings) {
5542         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5543         startedFromSetupPosition = TRUE;
5544     }
5545     if(startedFromPositionFile) {
5546       /* [HGM] loadPos: use PositionFile for every new game */
5547       CopyBoard(initialPosition, filePosition);
5548       for(i=0; i<nrCastlingRights; i++)
5549           initialRights[i] = filePosition[CASTLING][i];
5550       startedFromSetupPosition = TRUE;
5551     }
5552
5553     CopyBoard(boards[0], initialPosition);
5554
5555     if(oldx != gameInfo.boardWidth ||
5556        oldy != gameInfo.boardHeight ||
5557        oldh != gameInfo.holdingsWidth
5558 #ifdef GOTHIC
5559        || oldv == VariantGothic ||        // For licensing popups
5560        gameInfo.variant == VariantGothic
5561 #endif
5562 #ifdef FALCON
5563        || oldv == VariantFalcon ||
5564        gameInfo.variant == VariantFalcon
5565 #endif
5566                                          )
5567             InitDrawingSizes(-2 ,0);
5568
5569     if (redraw)
5570       DrawPosition(TRUE, boards[currentMove]);
5571 }
5572
5573 void
5574 SendBoard(cps, moveNum)
5575      ChessProgramState *cps;
5576      int moveNum;
5577 {
5578     char message[MSG_SIZ];
5579
5580     if (cps->useSetboard) {
5581       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5582       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5583       SendToProgram(message, cps);
5584       free(fen);
5585
5586     } else {
5587       ChessSquare *bp;
5588       int i, j;
5589       /* Kludge to set black to move, avoiding the troublesome and now
5590        * deprecated "black" command.
5591        */
5592       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5593         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5594
5595       SendToProgram("edit\n", cps);
5596       SendToProgram("#\n", cps);
5597       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5598         bp = &boards[moveNum][i][BOARD_LEFT];
5599         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5600           if ((int) *bp < (int) BlackPawn) {
5601             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5602                     AAA + j, ONE + i);
5603             if(message[0] == '+' || message[0] == '~') {
5604               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5605                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5606                         AAA + j, ONE + i);
5607             }
5608             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5609                 message[1] = BOARD_RGHT   - 1 - j + '1';
5610                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5611             }
5612             SendToProgram(message, cps);
5613           }
5614         }
5615       }
5616
5617       SendToProgram("c\n", cps);
5618       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5619         bp = &boards[moveNum][i][BOARD_LEFT];
5620         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5621           if (((int) *bp != (int) EmptySquare)
5622               && ((int) *bp >= (int) BlackPawn)) {
5623             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5624                     AAA + j, ONE + i);
5625             if(message[0] == '+' || message[0] == '~') {
5626               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5627                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5628                         AAA + j, ONE + i);
5629             }
5630             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5631                 message[1] = BOARD_RGHT   - 1 - j + '1';
5632                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5633             }
5634             SendToProgram(message, cps);
5635           }
5636         }
5637       }
5638
5639       SendToProgram(".\n", cps);
5640     }
5641     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5642 }
5643
5644 static int autoQueen; // [HGM] oneclick
5645
5646 int
5647 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5648 {
5649     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5650     /* [HGM] add Shogi promotions */
5651     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5652     ChessSquare piece;
5653     ChessMove moveType;
5654     Boolean premove;
5655
5656     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5657     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5658
5659     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5660       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5661         return FALSE;
5662
5663     piece = boards[currentMove][fromY][fromX];
5664     if(gameInfo.variant == VariantShogi) {
5665         promotionZoneSize = BOARD_HEIGHT/3;
5666         highestPromotingPiece = (int)WhiteFerz;
5667     } else if(gameInfo.variant == VariantMakruk) {
5668         promotionZoneSize = 3;
5669     }
5670
5671     // next weed out all moves that do not touch the promotion zone at all
5672     if((int)piece >= BlackPawn) {
5673         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5674              return FALSE;
5675         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5676     } else {
5677         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5678            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5679     }
5680
5681     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5682
5683     // weed out mandatory Shogi promotions
5684     if(gameInfo.variant == VariantShogi) {
5685         if(piece >= BlackPawn) {
5686             if(toY == 0 && piece == BlackPawn ||
5687                toY == 0 && piece == BlackQueen ||
5688                toY <= 1 && piece == BlackKnight) {
5689                 *promoChoice = '+';
5690                 return FALSE;
5691             }
5692         } else {
5693             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5694                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5695                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5696                 *promoChoice = '+';
5697                 return FALSE;
5698             }
5699         }
5700     }
5701
5702     // weed out obviously illegal Pawn moves
5703     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5704         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5705         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5706         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5707         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5708         // note we are not allowed to test for valid (non-)capture, due to premove
5709     }
5710
5711     // we either have a choice what to promote to, or (in Shogi) whether to promote
5712     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5713         *promoChoice = PieceToChar(BlackFerz);  // no choice
5714         return FALSE;
5715     }
5716     // no sense asking what we must promote to if it is going to explode...
5717     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5718         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5719         return FALSE;
5720     }
5721     if(autoQueen) { // predetermined
5722         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5723              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5724         else *promoChoice = PieceToChar(BlackQueen);
5725         return FALSE;
5726     }
5727
5728     // suppress promotion popup on illegal moves that are not premoves
5729     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5730               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5731     if(appData.testLegality && !premove) {
5732         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5733                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5734         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5735             return FALSE;
5736     }
5737
5738     return TRUE;
5739 }
5740
5741 int
5742 InPalace(row, column)
5743      int row, column;
5744 {   /* [HGM] for Xiangqi */
5745     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5746          column < (BOARD_WIDTH + 4)/2 &&
5747          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5748     return FALSE;
5749 }
5750
5751 int
5752 PieceForSquare (x, y)
5753      int x;
5754      int y;
5755 {
5756   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5757      return -1;
5758   else
5759      return boards[currentMove][y][x];
5760 }
5761
5762 int
5763 OKToStartUserMove(x, y)
5764      int x, y;
5765 {
5766     ChessSquare from_piece;
5767     int white_piece;
5768
5769     if (matchMode) return FALSE;
5770     if (gameMode == EditPosition) return TRUE;
5771
5772     if (x >= 0 && y >= 0)
5773       from_piece = boards[currentMove][y][x];
5774     else
5775       from_piece = EmptySquare;
5776
5777     if (from_piece == EmptySquare) return FALSE;
5778
5779     white_piece = (int)from_piece >= (int)WhitePawn &&
5780       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5781
5782     switch (gameMode) {
5783       case PlayFromGameFile:
5784       case AnalyzeFile:
5785       case TwoMachinesPlay:
5786       case EndOfGame:
5787         return FALSE;
5788
5789       case IcsObserving:
5790       case IcsIdle:
5791         return FALSE;
5792
5793       case MachinePlaysWhite:
5794       case IcsPlayingBlack:
5795         if (appData.zippyPlay) return FALSE;
5796         if (white_piece) {
5797             DisplayMoveError(_("You are playing Black"));
5798             return FALSE;
5799         }
5800         break;
5801
5802       case MachinePlaysBlack:
5803       case IcsPlayingWhite:
5804         if (appData.zippyPlay) return FALSE;
5805         if (!white_piece) {
5806             DisplayMoveError(_("You are playing White"));
5807             return FALSE;
5808         }
5809         break;
5810
5811       case EditGame:
5812         if (!white_piece && WhiteOnMove(currentMove)) {
5813             DisplayMoveError(_("It is White's turn"));
5814             return FALSE;
5815         }
5816         if (white_piece && !WhiteOnMove(currentMove)) {
5817             DisplayMoveError(_("It is Black's turn"));
5818             return FALSE;
5819         }
5820         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5821             /* Editing correspondence game history */
5822             /* Could disallow this or prompt for confirmation */
5823             cmailOldMove = -1;
5824         }
5825         break;
5826
5827       case BeginningOfGame:
5828         if (appData.icsActive) return FALSE;
5829         if (!appData.noChessProgram) {
5830             if (!white_piece) {
5831                 DisplayMoveError(_("You are playing White"));
5832                 return FALSE;
5833             }
5834         }
5835         break;
5836
5837       case Training:
5838         if (!white_piece && WhiteOnMove(currentMove)) {
5839             DisplayMoveError(_("It is White's turn"));
5840             return FALSE;
5841         }
5842         if (white_piece && !WhiteOnMove(currentMove)) {
5843             DisplayMoveError(_("It is Black's turn"));
5844             return FALSE;
5845         }
5846         break;
5847
5848       default:
5849       case IcsExamining:
5850         break;
5851     }
5852     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5853         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5854         && gameMode != AnalyzeFile && gameMode != Training) {
5855         DisplayMoveError(_("Displayed position is not current"));
5856         return FALSE;
5857     }
5858     return TRUE;
5859 }
5860
5861 Boolean
5862 OnlyMove(int *x, int *y, Boolean captures) {
5863     DisambiguateClosure cl;
5864     if (appData.zippyPlay) return FALSE;
5865     switch(gameMode) {
5866       case MachinePlaysBlack:
5867       case IcsPlayingWhite:
5868       case BeginningOfGame:
5869         if(!WhiteOnMove(currentMove)) return FALSE;
5870         break;
5871       case MachinePlaysWhite:
5872       case IcsPlayingBlack:
5873         if(WhiteOnMove(currentMove)) return FALSE;
5874         break;
5875       case EditGame:
5876         break;
5877       default:
5878         return FALSE;
5879     }
5880     cl.pieceIn = EmptySquare;
5881     cl.rfIn = *y;
5882     cl.ffIn = *x;
5883     cl.rtIn = -1;
5884     cl.ftIn = -1;
5885     cl.promoCharIn = NULLCHAR;
5886     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5887     if( cl.kind == NormalMove ||
5888         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5889         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5890         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5891       fromX = cl.ff;
5892       fromY = cl.rf;
5893       *x = cl.ft;
5894       *y = cl.rt;
5895       return TRUE;
5896     }
5897     if(cl.kind != ImpossibleMove) return FALSE;
5898     cl.pieceIn = EmptySquare;
5899     cl.rfIn = -1;
5900     cl.ffIn = -1;
5901     cl.rtIn = *y;
5902     cl.ftIn = *x;
5903     cl.promoCharIn = NULLCHAR;
5904     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5905     if( cl.kind == NormalMove ||
5906         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5907         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5908         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5909       fromX = cl.ff;
5910       fromY = cl.rf;
5911       *x = cl.ft;
5912       *y = cl.rt;
5913       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5914       return TRUE;
5915     }
5916     return FALSE;
5917 }
5918
5919 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5920 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5921 int lastLoadGameUseList = FALSE;
5922 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5923 ChessMove lastLoadGameStart = EndOfFile;
5924
5925 void
5926 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5927      int fromX, fromY, toX, toY;
5928      int promoChar;
5929 {
5930     ChessMove moveType;
5931     ChessSquare pdown, pup;
5932
5933     /* Check if the user is playing in turn.  This is complicated because we
5934        let the user "pick up" a piece before it is his turn.  So the piece he
5935        tried to pick up may have been captured by the time he puts it down!
5936        Therefore we use the color the user is supposed to be playing in this
5937        test, not the color of the piece that is currently on the starting
5938        square---except in EditGame mode, where the user is playing both
5939        sides; fortunately there the capture race can't happen.  (It can
5940        now happen in IcsExamining mode, but that's just too bad.  The user
5941        will get a somewhat confusing message in that case.)
5942        */
5943
5944     switch (gameMode) {
5945       case PlayFromGameFile:
5946       case AnalyzeFile:
5947       case TwoMachinesPlay:
5948       case EndOfGame:
5949       case IcsObserving:
5950       case IcsIdle:
5951         /* We switched into a game mode where moves are not accepted,
5952            perhaps while the mouse button was down. */
5953         return;
5954
5955       case MachinePlaysWhite:
5956         /* User is moving for Black */
5957         if (WhiteOnMove(currentMove)) {
5958             DisplayMoveError(_("It is White's turn"));
5959             return;
5960         }
5961         break;
5962
5963       case MachinePlaysBlack:
5964         /* User is moving for White */
5965         if (!WhiteOnMove(currentMove)) {
5966             DisplayMoveError(_("It is Black's turn"));
5967             return;
5968         }
5969         break;
5970
5971       case EditGame:
5972       case IcsExamining:
5973       case BeginningOfGame:
5974       case AnalyzeMode:
5975       case Training:
5976         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5977             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5978             /* User is moving for Black */
5979             if (WhiteOnMove(currentMove)) {
5980                 DisplayMoveError(_("It is White's turn"));
5981                 return;
5982             }
5983         } else {
5984             /* User is moving for White */
5985             if (!WhiteOnMove(currentMove)) {
5986                 DisplayMoveError(_("It is Black's turn"));
5987                 return;
5988             }
5989         }
5990         break;
5991
5992       case IcsPlayingBlack:
5993         /* User is moving for Black */
5994         if (WhiteOnMove(currentMove)) {
5995             if (!appData.premove) {
5996                 DisplayMoveError(_("It is White's turn"));
5997             } else if (toX >= 0 && toY >= 0) {
5998                 premoveToX = toX;
5999                 premoveToY = toY;
6000                 premoveFromX = fromX;
6001                 premoveFromY = fromY;
6002                 premovePromoChar = promoChar;
6003                 gotPremove = 1;
6004                 if (appData.debugMode)
6005                     fprintf(debugFP, "Got premove: fromX %d,"
6006                             "fromY %d, toX %d, toY %d\n",
6007                             fromX, fromY, toX, toY);
6008             }
6009             return;
6010         }
6011         break;
6012
6013       case IcsPlayingWhite:
6014         /* User is moving for White */
6015         if (!WhiteOnMove(currentMove)) {
6016             if (!appData.premove) {
6017                 DisplayMoveError(_("It is Black's turn"));
6018             } else if (toX >= 0 && toY >= 0) {
6019                 premoveToX = toX;
6020                 premoveToY = toY;
6021                 premoveFromX = fromX;
6022                 premoveFromY = fromY;
6023                 premovePromoChar = promoChar;
6024                 gotPremove = 1;
6025                 if (appData.debugMode)
6026                     fprintf(debugFP, "Got premove: fromX %d,"
6027                             "fromY %d, toX %d, toY %d\n",
6028                             fromX, fromY, toX, toY);
6029             }
6030             return;
6031         }
6032         break;
6033
6034       default:
6035         break;
6036
6037       case EditPosition:
6038         /* EditPosition, empty square, or different color piece;
6039            click-click move is possible */
6040         if (toX == -2 || toY == -2) {
6041             boards[0][fromY][fromX] = EmptySquare;
6042             DrawPosition(FALSE, boards[currentMove]);
6043             return;
6044         } else if (toX >= 0 && toY >= 0) {
6045             boards[0][toY][toX] = boards[0][fromY][fromX];
6046             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6047                 if(boards[0][fromY][0] != EmptySquare) {
6048                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6049                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6050                 }
6051             } else
6052             if(fromX == BOARD_RGHT+1) {
6053                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6054                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6055                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6056                 }
6057             } else
6058             boards[0][fromY][fromX] = EmptySquare;
6059             DrawPosition(FALSE, boards[currentMove]);
6060             return;
6061         }
6062         return;
6063     }
6064
6065     if(toX < 0 || toY < 0) return;
6066     pdown = boards[currentMove][fromY][fromX];
6067     pup = boards[currentMove][toY][toX];
6068
6069     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6070     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6071          if( pup != EmptySquare ) return;
6072          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6073            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6074                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6075            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6076            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6077            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6078            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6079          fromY = DROP_RANK;
6080     }
6081
6082     /* [HGM] always test for legality, to get promotion info */
6083     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6084                                          fromY, fromX, toY, toX, promoChar);
6085     /* [HGM] but possibly ignore an IllegalMove result */
6086     if (appData.testLegality) {
6087         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6088             DisplayMoveError(_("Illegal move"));
6089             return;
6090         }
6091     }
6092
6093     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6094 }
6095
6096 /* Common tail of UserMoveEvent and DropMenuEvent */
6097 int
6098 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6099      ChessMove moveType;
6100      int fromX, fromY, toX, toY;
6101      /*char*/int promoChar;
6102 {
6103     char *bookHit = 0;
6104
6105     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6106         // [HGM] superchess: suppress promotions to non-available piece
6107         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6108         if(WhiteOnMove(currentMove)) {
6109             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6110         } else {
6111             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6112         }
6113     }
6114
6115     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6116        move type in caller when we know the move is a legal promotion */
6117     if(moveType == NormalMove && promoChar)
6118         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6119
6120     /* [HGM] <popupFix> The following if has been moved here from
6121        UserMoveEvent(). Because it seemed to belong here (why not allow
6122        piece drops in training games?), and because it can only be
6123        performed after it is known to what we promote. */
6124     if (gameMode == Training) {
6125       /* compare the move played on the board to the next move in the
6126        * game. If they match, display the move and the opponent's response.
6127        * If they don't match, display an error message.
6128        */
6129       int saveAnimate;
6130       Board testBoard;
6131       CopyBoard(testBoard, boards[currentMove]);
6132       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6133
6134       if (CompareBoards(testBoard, boards[currentMove+1])) {
6135         ForwardInner(currentMove+1);
6136
6137         /* Autoplay the opponent's response.
6138          * if appData.animate was TRUE when Training mode was entered,
6139          * the response will be animated.
6140          */
6141         saveAnimate = appData.animate;
6142         appData.animate = animateTraining;
6143         ForwardInner(currentMove+1);
6144         appData.animate = saveAnimate;
6145
6146         /* check for the end of the game */
6147         if (currentMove >= forwardMostMove) {
6148           gameMode = PlayFromGameFile;
6149           ModeHighlight();
6150           SetTrainingModeOff();
6151           DisplayInformation(_("End of game"));
6152         }
6153       } else {
6154         DisplayError(_("Incorrect move"), 0);
6155       }
6156       return 1;
6157     }
6158
6159   /* Ok, now we know that the move is good, so we can kill
6160      the previous line in Analysis Mode */
6161   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6162                                 && currentMove < forwardMostMove) {
6163     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6164     else forwardMostMove = currentMove;
6165   }
6166
6167   /* If we need the chess program but it's dead, restart it */
6168   ResurrectChessProgram();
6169
6170   /* A user move restarts a paused game*/
6171   if (pausing)
6172     PauseEvent();
6173
6174   thinkOutput[0] = NULLCHAR;
6175
6176   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6177
6178   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6179
6180   if (gameMode == BeginningOfGame) {
6181     if (appData.noChessProgram) {
6182       gameMode = EditGame;
6183       SetGameInfo();
6184     } else {
6185       char buf[MSG_SIZ];
6186       gameMode = MachinePlaysBlack;
6187       StartClocks();
6188       SetGameInfo();
6189       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6190       DisplayTitle(buf);
6191       if (first.sendName) {
6192         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6193         SendToProgram(buf, &first);
6194       }
6195       StartClocks();
6196     }
6197     ModeHighlight();
6198   }
6199
6200   /* Relay move to ICS or chess engine */
6201   if (appData.icsActive) {
6202     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6203         gameMode == IcsExamining) {
6204       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6205         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6206         SendToICS("draw ");
6207         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6208       }
6209       // also send plain move, in case ICS does not understand atomic claims
6210       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6211       ics_user_moved = 1;
6212     }
6213   } else {
6214     if (first.sendTime && (gameMode == BeginningOfGame ||
6215                            gameMode == MachinePlaysWhite ||
6216                            gameMode == MachinePlaysBlack)) {
6217       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6218     }
6219     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6220          // [HGM] book: if program might be playing, let it use book
6221         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6222         first.maybeThinking = TRUE;
6223     } else SendMoveToProgram(forwardMostMove-1, &first);
6224     if (currentMove == cmailOldMove + 1) {
6225       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6226     }
6227   }
6228
6229   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6230
6231   switch (gameMode) {
6232   case EditGame:
6233     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6234     case MT_NONE:
6235     case MT_CHECK:
6236       break;
6237     case MT_CHECKMATE:
6238     case MT_STAINMATE:
6239       if (WhiteOnMove(currentMove)) {
6240         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6241       } else {
6242         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6243       }
6244       break;
6245     case MT_STALEMATE:
6246       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6247       break;
6248     }
6249     break;
6250
6251   case MachinePlaysBlack:
6252   case MachinePlaysWhite:
6253     /* disable certain menu options while machine is thinking */
6254     SetMachineThinkingEnables();
6255     break;
6256
6257   default:
6258     break;
6259   }
6260
6261   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6262
6263   if(bookHit) { // [HGM] book: simulate book reply
6264         static char bookMove[MSG_SIZ]; // a bit generous?
6265
6266         programStats.nodes = programStats.depth = programStats.time =
6267         programStats.score = programStats.got_only_move = 0;
6268         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6269
6270         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6271         strcat(bookMove, bookHit);
6272         HandleMachineMove(bookMove, &first);
6273   }
6274   return 1;
6275 }
6276
6277 void
6278 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6279      Board board;
6280      int flags;
6281      ChessMove kind;
6282      int rf, ff, rt, ft;
6283      VOIDSTAR closure;
6284 {
6285     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6286     Markers *m = (Markers *) closure;
6287     if(rf == fromY && ff == fromX)
6288         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6289                          || kind == WhiteCapturesEnPassant
6290                          || kind == BlackCapturesEnPassant);
6291     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6292 }
6293
6294 void
6295 MarkTargetSquares(int clear)
6296 {
6297   int x, y;
6298   if(!appData.markers || !appData.highlightDragging ||
6299      !appData.testLegality || gameMode == EditPosition) return;
6300   if(clear) {
6301     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6302   } else {
6303     int capt = 0;
6304     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6305     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6306       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6307       if(capt)
6308       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6309     }
6310   }
6311   DrawPosition(TRUE, NULL);
6312 }
6313
6314 int
6315 Explode(Board board, int fromX, int fromY, int toX, int toY)
6316 {
6317     if(gameInfo.variant == VariantAtomic &&
6318        (board[toY][toX] != EmptySquare ||                     // capture?
6319         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6320                          board[fromY][fromX] == BlackPawn   )
6321       )) {
6322         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6323         return TRUE;
6324     }
6325     return FALSE;
6326 }
6327
6328 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6329
6330 void LeftClick(ClickType clickType, int xPix, int yPix)
6331 {
6332     int x, y;
6333     Boolean saveAnimate;
6334     static int second = 0, promotionChoice = 0, dragging = 0;
6335     char promoChoice = NULLCHAR;
6336
6337     if(appData.seekGraph && appData.icsActive && loggedOn &&
6338         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6339         SeekGraphClick(clickType, xPix, yPix, 0);
6340         return;
6341     }
6342
6343     if (clickType == Press) ErrorPopDown();
6344     MarkTargetSquares(1);
6345
6346     x = EventToSquare(xPix, BOARD_WIDTH);
6347     y = EventToSquare(yPix, BOARD_HEIGHT);
6348     if (!flipView && y >= 0) {
6349         y = BOARD_HEIGHT - 1 - y;
6350     }
6351     if (flipView && x >= 0) {
6352         x = BOARD_WIDTH - 1 - x;
6353     }
6354
6355     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6356         if(clickType == Release) return; // ignore upclick of click-click destination
6357         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6358         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6359         if(gameInfo.holdingsWidth &&
6360                 (WhiteOnMove(currentMove)
6361                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6362                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6363             // click in right holdings, for determining promotion piece
6364             ChessSquare p = boards[currentMove][y][x];
6365             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6366             if(p != EmptySquare) {
6367                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6368                 fromX = fromY = -1;
6369                 return;
6370             }
6371         }
6372         DrawPosition(FALSE, boards[currentMove]);
6373         return;
6374     }
6375
6376     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6377     if(clickType == Press
6378             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6379               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6380               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6381         return;
6382
6383     autoQueen = appData.alwaysPromoteToQueen;
6384
6385     if (fromX == -1) {
6386       gatingPiece = EmptySquare;
6387       if (clickType != Press) {
6388         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6389             DragPieceEnd(xPix, yPix); dragging = 0;
6390             DrawPosition(FALSE, NULL);
6391         }
6392         return;
6393       }
6394       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6395             /* First square */
6396             if (OKToStartUserMove(x, y)) {
6397                 fromX = x;
6398                 fromY = y;
6399                 second = 0;
6400                 MarkTargetSquares(0);
6401                 DragPieceBegin(xPix, yPix); dragging = 1;
6402                 if (appData.highlightDragging) {
6403                     SetHighlights(x, y, -1, -1);
6404                 }
6405             }
6406             return;
6407         }
6408     }
6409
6410     /* fromX != -1 */
6411     if (clickType == Press && gameMode != EditPosition) {
6412         ChessSquare fromP;
6413         ChessSquare toP;
6414         int frc;
6415
6416         // ignore off-board to clicks
6417         if(y < 0 || x < 0) return;
6418
6419         /* Check if clicking again on the same color piece */
6420         fromP = boards[currentMove][fromY][fromX];
6421         toP = boards[currentMove][y][x];
6422         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6423         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6424              WhitePawn <= toP && toP <= WhiteKing &&
6425              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6426              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6427             (BlackPawn <= fromP && fromP <= BlackKing &&
6428              BlackPawn <= toP && toP <= BlackKing &&
6429              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6430              !(fromP == BlackKing && toP == BlackRook && frc))) {
6431             /* Clicked again on same color piece -- changed his mind */
6432             second = (x == fromX && y == fromY);
6433            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6434             if (appData.highlightDragging) {
6435                 SetHighlights(x, y, -1, -1);
6436             } else {
6437                 ClearHighlights();
6438             }
6439             if (OKToStartUserMove(x, y)) {
6440                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6441                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6442                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6443                  gatingPiece = boards[currentMove][fromY][fromX];
6444                 else gatingPiece = EmptySquare;
6445                 fromX = x;
6446                 fromY = y; dragging = 1;
6447                 MarkTargetSquares(0);
6448                 DragPieceBegin(xPix, yPix);
6449             }
6450            }
6451            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6452            second = FALSE; 
6453         }
6454         // ignore clicks on holdings
6455         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6456     }
6457
6458     if (clickType == Release && x == fromX && y == fromY) {
6459         DragPieceEnd(xPix, yPix); dragging = 0;
6460         if (appData.animateDragging) {
6461             /* Undo animation damage if any */
6462             DrawPosition(FALSE, NULL);
6463         }
6464         if (second) {
6465             /* Second up/down in same square; just abort move */
6466             second = 0;
6467             fromX = fromY = -1;
6468             gatingPiece = EmptySquare;
6469             ClearHighlights();
6470             gotPremove = 0;
6471             ClearPremoveHighlights();
6472         } else {
6473             /* First upclick in same square; start click-click mode */
6474             SetHighlights(x, y, -1, -1);
6475         }
6476         return;
6477     }
6478
6479     /* we now have a different from- and (possibly off-board) to-square */
6480     /* Completed move */
6481     toX = x;
6482     toY = y;
6483     saveAnimate = appData.animate;
6484     if (clickType == Press) {
6485         /* Finish clickclick move */
6486         if (appData.animate || appData.highlightLastMove) {
6487             SetHighlights(fromX, fromY, toX, toY);
6488         } else {
6489             ClearHighlights();
6490         }
6491     } else {
6492         /* Finish drag move */
6493         if (appData.highlightLastMove) {
6494             SetHighlights(fromX, fromY, toX, toY);
6495         } else {
6496             ClearHighlights();
6497         }
6498         DragPieceEnd(xPix, yPix); dragging = 0;
6499         /* Don't animate move and drag both */
6500         appData.animate = FALSE;
6501     }
6502
6503     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6504     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6505         ChessSquare piece = boards[currentMove][fromY][fromX];
6506         if(gameMode == EditPosition && piece != EmptySquare &&
6507            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6508             int n;
6509
6510             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6511                 n = PieceToNumber(piece - (int)BlackPawn);
6512                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6513                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6514                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6515             } else
6516             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6517                 n = PieceToNumber(piece);
6518                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6519                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6520                 boards[currentMove][n][BOARD_WIDTH-2]++;
6521             }
6522             boards[currentMove][fromY][fromX] = EmptySquare;
6523         }
6524         ClearHighlights();
6525         fromX = fromY = -1;
6526         DrawPosition(TRUE, boards[currentMove]);
6527         return;
6528     }
6529
6530     // off-board moves should not be highlighted
6531     if(x < 0 || y < 0) ClearHighlights();
6532
6533     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6534
6535     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6536         SetHighlights(fromX, fromY, toX, toY);
6537         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6538             // [HGM] super: promotion to captured piece selected from holdings
6539             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6540             promotionChoice = TRUE;
6541             // kludge follows to temporarily execute move on display, without promoting yet
6542             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6543             boards[currentMove][toY][toX] = p;
6544             DrawPosition(FALSE, boards[currentMove]);
6545             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6546             boards[currentMove][toY][toX] = q;
6547             DisplayMessage("Click in holdings to choose piece", "");
6548             return;
6549         }
6550         PromotionPopUp();
6551     } else {
6552         int oldMove = currentMove;
6553         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6554         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6555         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6556         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6557            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6558             DrawPosition(TRUE, boards[currentMove]);
6559         fromX = fromY = -1;
6560     }
6561     appData.animate = saveAnimate;
6562     if (appData.animate || appData.animateDragging) {
6563         /* Undo animation damage if needed */
6564         DrawPosition(FALSE, NULL);
6565     }
6566 }
6567
6568 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6569 {   // front-end-free part taken out of PieceMenuPopup
6570     int whichMenu; int xSqr, ySqr;
6571
6572     if(seekGraphUp) { // [HGM] seekgraph
6573         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6574         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6575         return -2;
6576     }
6577
6578     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6579          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6580         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6581         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6582         if(action == Press)   {
6583             originalFlip = flipView;
6584             flipView = !flipView; // temporarily flip board to see game from partners perspective
6585             DrawPosition(TRUE, partnerBoard);
6586             DisplayMessage(partnerStatus, "");
6587             partnerUp = TRUE;
6588         } else if(action == Release) {
6589             flipView = originalFlip;
6590             DrawPosition(TRUE, boards[currentMove]);
6591             partnerUp = FALSE;
6592         }
6593         return -2;
6594     }
6595
6596     xSqr = EventToSquare(x, BOARD_WIDTH);
6597     ySqr = EventToSquare(y, BOARD_HEIGHT);
6598     if (action == Release) UnLoadPV(); // [HGM] pv
6599     if (action != Press) return -2; // return code to be ignored
6600     switch (gameMode) {
6601       case IcsExamining:
6602         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6603       case EditPosition:
6604         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6605         if (xSqr < 0 || ySqr < 0) return -1;\r
6606         whichMenu = 0; // edit-position menu
6607         break;
6608       case IcsObserving:
6609         if(!appData.icsEngineAnalyze) return -1;
6610       case IcsPlayingWhite:
6611       case IcsPlayingBlack:
6612         if(!appData.zippyPlay) goto noZip;
6613       case AnalyzeMode:
6614       case AnalyzeFile:
6615       case MachinePlaysWhite:
6616       case MachinePlaysBlack:
6617       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6618         if (!appData.dropMenu) {
6619           LoadPV(x, y);
6620           return 2; // flag front-end to grab mouse events
6621         }
6622         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6623            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6624       case EditGame:
6625       noZip:
6626         if (xSqr < 0 || ySqr < 0) return -1;
6627         if (!appData.dropMenu || appData.testLegality &&
6628             gameInfo.variant != VariantBughouse &&
6629             gameInfo.variant != VariantCrazyhouse) return -1;
6630         whichMenu = 1; // drop menu
6631         break;
6632       default:
6633         return -1;
6634     }
6635
6636     if (((*fromX = xSqr) < 0) ||
6637         ((*fromY = ySqr) < 0)) {
6638         *fromX = *fromY = -1;
6639         return -1;
6640     }
6641     if (flipView)
6642       *fromX = BOARD_WIDTH - 1 - *fromX;
6643     else
6644       *fromY = BOARD_HEIGHT - 1 - *fromY;
6645
6646     return whichMenu;
6647 }
6648
6649 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6650 {
6651 //    char * hint = lastHint;
6652     FrontEndProgramStats stats;
6653
6654     stats.which = cps == &first ? 0 : 1;
6655     stats.depth = cpstats->depth;
6656     stats.nodes = cpstats->nodes;
6657     stats.score = cpstats->score;
6658     stats.time = cpstats->time;
6659     stats.pv = cpstats->movelist;
6660     stats.hint = lastHint;
6661     stats.an_move_index = 0;
6662     stats.an_move_count = 0;
6663
6664     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6665         stats.hint = cpstats->move_name;
6666         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6667         stats.an_move_count = cpstats->nr_moves;
6668     }
6669
6670     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6671
6672     SetProgramStats( &stats );
6673 }
6674
6675 void
6676 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6677 {       // count all piece types
6678         int p, f, r;
6679         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6680         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6681         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6682                 p = board[r][f];
6683                 pCnt[p]++;
6684                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6685                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6686                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6687                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6688                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6689                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6690         }
6691 }
6692
6693 int
6694 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6695 {
6696         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6697         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6698
6699         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6700         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6701         if(myPawns == 2 && nMine == 3) // KPP
6702             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6703         if(myPawns == 1 && nMine == 2) // KP
6704             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6705         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6706             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6707         if(myPawns) return FALSE;
6708         if(pCnt[WhiteRook+side])
6709             return pCnt[BlackRook-side] ||
6710                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6711                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6712                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6713         if(pCnt[WhiteCannon+side]) {
6714             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6715             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6716         }
6717         if(pCnt[WhiteKnight+side])
6718             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6719         return FALSE;
6720 }
6721
6722 int
6723 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6724 {
6725         VariantClass v = gameInfo.variant;
6726
6727         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6728         if(v == VariantShatranj) return TRUE; // always winnable through baring
6729         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6730         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6731
6732         if(v == VariantXiangqi) {
6733                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6734
6735                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6736                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6737                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6738                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6739                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6740                 if(stale) // we have at least one last-rank P plus perhaps C
6741                     return majors // KPKX
6742                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6743                 else // KCA*E*
6744                     return pCnt[WhiteFerz+side] // KCAK
6745                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6746                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6747                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6748
6749         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6750                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6751
6752                 if(nMine == 1) return FALSE; // bare King
6753                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6754                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6755                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6756                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6757                 if(pCnt[WhiteKnight+side])
6758                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6759                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6760                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6761                 if(nBishops)
6762                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6763                 if(pCnt[WhiteAlfil+side])
6764                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6765                 if(pCnt[WhiteWazir+side])
6766                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6767         }
6768
6769         return TRUE;
6770 }
6771
6772 int
6773 Adjudicate(ChessProgramState *cps)
6774 {       // [HGM] some adjudications useful with buggy engines
6775         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6776         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6777         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6778         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6779         int k, count = 0; static int bare = 1;
6780         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6781         Boolean canAdjudicate = !appData.icsActive;
6782
6783         // most tests only when we understand the game, i.e. legality-checking on
6784             if( appData.testLegality )
6785             {   /* [HGM] Some more adjudications for obstinate engines */
6786                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6787                 static int moveCount = 6;
6788                 ChessMove result;
6789                 char *reason = NULL;
6790
6791                 /* Count what is on board. */
6792                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6793
6794                 /* Some material-based adjudications that have to be made before stalemate test */
6795                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6796                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6797                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6798                      if(canAdjudicate && appData.checkMates) {
6799                          if(engineOpponent)
6800                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6801                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6802                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6803                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6804                          return 1;
6805                      }
6806                 }
6807
6808                 /* Bare King in Shatranj (loses) or Losers (wins) */
6809                 if( nrW == 1 || nrB == 1) {
6810                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6811                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6812                      if(canAdjudicate && appData.checkMates) {
6813                          if(engineOpponent)
6814                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6815                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6816                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6817                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6818                          return 1;
6819                      }
6820                   } else
6821                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6822                   {    /* bare King */
6823                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6824                         if(canAdjudicate && appData.checkMates) {
6825                             /* but only adjudicate if adjudication enabled */
6826                             if(engineOpponent)
6827                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6828                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6829                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6830                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6831                             return 1;
6832                         }
6833                   }
6834                 } else bare = 1;
6835
6836
6837             // don't wait for engine to announce game end if we can judge ourselves
6838             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6839               case MT_CHECK:
6840                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6841                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6842                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6843                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6844                             checkCnt++;
6845                         if(checkCnt >= 2) {
6846                             reason = "Xboard adjudication: 3rd check";
6847                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6848                             break;
6849                         }
6850                     }
6851                 }
6852               case MT_NONE:
6853               default:
6854                 break;
6855               case MT_STALEMATE:
6856               case MT_STAINMATE:
6857                 reason = "Xboard adjudication: Stalemate";
6858                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6859                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6860                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6861                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6862                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6863                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6864                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6865                                                                         EP_CHECKMATE : EP_WINS);
6866                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6867                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6868                 }
6869                 break;
6870               case MT_CHECKMATE:
6871                 reason = "Xboard adjudication: Checkmate";
6872                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6873                 break;
6874             }
6875
6876                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6877                     case EP_STALEMATE:
6878                         result = GameIsDrawn; break;
6879                     case EP_CHECKMATE:
6880                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6881                     case EP_WINS:
6882                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6883                     default:
6884                         result = EndOfFile;
6885                 }
6886                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6887                     if(engineOpponent)
6888                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6889                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6890                     GameEnds( result, reason, GE_XBOARD );
6891                     return 1;
6892                 }
6893
6894                 /* Next absolutely insufficient mating material. */
6895                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6896                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6897                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6898
6899                      /* always flag draws, for judging claims */
6900                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6901
6902                      if(canAdjudicate && appData.materialDraws) {
6903                          /* but only adjudicate them if adjudication enabled */
6904                          if(engineOpponent) {
6905                            SendToProgram("force\n", engineOpponent); // suppress reply
6906                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6907                          }
6908                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6909                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6910                          return 1;
6911                      }
6912                 }
6913
6914                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6915                 if(gameInfo.variant == VariantXiangqi ?
6916                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6917                  : nrW + nrB == 4 &&
6918                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6919                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6920                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6921                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6922                    ) ) {
6923                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6924                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6925                           if(engineOpponent) {
6926                             SendToProgram("force\n", engineOpponent); // suppress reply
6927                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6928                           }
6929                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6930                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6931                           return 1;
6932                      }
6933                 } else moveCount = 6;
6934             }
6935         if (appData.debugMode) { int i;
6936             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6937                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6938                     appData.drawRepeats);
6939             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6940               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6941
6942         }
6943
6944         // Repetition draws and 50-move rule can be applied independently of legality testing
6945
6946                 /* Check for rep-draws */
6947                 count = 0;
6948                 for(k = forwardMostMove-2;
6949                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6950                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6951                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6952                     k-=2)
6953                 {   int rights=0;
6954                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6955                         /* compare castling rights */
6956                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6957                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6958                                 rights++; /* King lost rights, while rook still had them */
6959                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6960                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6961                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6962                                    rights++; /* but at least one rook lost them */
6963                         }
6964                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6965                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6966                                 rights++;
6967                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6968                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6969                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6970                                    rights++;
6971                         }
6972                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6973                             && appData.drawRepeats > 1) {
6974                              /* adjudicate after user-specified nr of repeats */
6975                              int result = GameIsDrawn;
6976                              char *details = "XBoard adjudication: repetition draw";
6977                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6978                                 // [HGM] xiangqi: check for forbidden perpetuals
6979                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6980                                 for(m=forwardMostMove; m>k; m-=2) {
6981                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6982                                         ourPerpetual = 0; // the current mover did not always check
6983                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6984                                         hisPerpetual = 0; // the opponent did not always check
6985                                 }
6986                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6987                                                                         ourPerpetual, hisPerpetual);
6988                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6989                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6990                                     details = "Xboard adjudication: perpetual checking";
6991                                 } else
6992                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6993                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6994                                 } else
6995                                 // Now check for perpetual chases
6996                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6997                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6998                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6999                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7000                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7001                                         details = "Xboard adjudication: perpetual chasing";
7002                                     } else
7003                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7004                                         break; // Abort repetition-checking loop.
7005                                 }
7006                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7007                              }
7008                              if(engineOpponent) {
7009                                SendToProgram("force\n", engineOpponent); // suppress reply
7010                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7011                              }
7012                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7013                              GameEnds( result, details, GE_XBOARD );
7014                              return 1;
7015                         }
7016                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7017                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7018                     }
7019                 }
7020
7021                 /* Now we test for 50-move draws. Determine ply count */
7022                 count = forwardMostMove;
7023                 /* look for last irreversble move */
7024                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7025                     count--;
7026                 /* if we hit starting position, add initial plies */
7027                 if( count == backwardMostMove )
7028                     count -= initialRulePlies;
7029                 count = forwardMostMove - count;
7030                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7031                         // adjust reversible move counter for checks in Xiangqi
7032                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7033                         if(i < backwardMostMove) i = backwardMostMove;
7034                         while(i <= forwardMostMove) {
7035                                 lastCheck = inCheck; // check evasion does not count
7036                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7037                                 if(inCheck || lastCheck) count--; // check does not count
7038                                 i++;
7039                         }
7040                 }
7041                 if( count >= 100)
7042                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7043                          /* this is used to judge if draw claims are legal */
7044                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7045                          if(engineOpponent) {
7046                            SendToProgram("force\n", engineOpponent); // suppress reply
7047                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7048                          }
7049                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7050                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7051                          return 1;
7052                 }
7053
7054                 /* if draw offer is pending, treat it as a draw claim
7055                  * when draw condition present, to allow engines a way to
7056                  * claim draws before making their move to avoid a race
7057                  * condition occurring after their move
7058                  */
7059                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7060                          char *p = NULL;
7061                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7062                              p = "Draw claim: 50-move rule";
7063                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7064                              p = "Draw claim: 3-fold repetition";
7065                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7066                              p = "Draw claim: insufficient mating material";
7067                          if( p != NULL && canAdjudicate) {
7068                              if(engineOpponent) {
7069                                SendToProgram("force\n", engineOpponent); // suppress reply
7070                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7071                              }
7072                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7073                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7074                              return 1;
7075                          }
7076                 }
7077
7078                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7079                     if(engineOpponent) {
7080                       SendToProgram("force\n", engineOpponent); // suppress reply
7081                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7082                     }
7083                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7084                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7085                     return 1;
7086                 }
7087         return 0;
7088 }
7089
7090 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7091 {   // [HGM] book: this routine intercepts moves to simulate book replies
7092     char *bookHit = NULL;
7093
7094     //first determine if the incoming move brings opponent into his book
7095     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7096         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7097     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7098     if(bookHit != NULL && !cps->bookSuspend) {
7099         // make sure opponent is not going to reply after receiving move to book position
7100         SendToProgram("force\n", cps);
7101         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7102     }
7103     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7104     // now arrange restart after book miss
7105     if(bookHit) {
7106         // after a book hit we never send 'go', and the code after the call to this routine
7107         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7108         char buf[MSG_SIZ];
7109         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7110         SendToProgram(buf, cps);
7111         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7112     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7113         SendToProgram("go\n", cps);
7114         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7115     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7116         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7117             SendToProgram("go\n", cps);
7118         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7119     }
7120     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7121 }
7122
7123 char *savedMessage;
7124 ChessProgramState *savedState;
7125 void DeferredBookMove(void)
7126 {
7127         if(savedState->lastPing != savedState->lastPong)
7128                     ScheduleDelayedEvent(DeferredBookMove, 10);
7129         else
7130         HandleMachineMove(savedMessage, savedState);
7131 }
7132
7133 void
7134 HandleMachineMove(message, cps)
7135      char *message;
7136      ChessProgramState *cps;
7137 {
7138     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7139     char realname[MSG_SIZ];
7140     int fromX, fromY, toX, toY;
7141     ChessMove moveType;
7142     char promoChar;
7143     char *p;
7144     int machineWhite;
7145     char *bookHit;
7146
7147     cps->userError = 0;
7148
7149 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7150     /*
7151      * Kludge to ignore BEL characters
7152      */
7153     while (*message == '\007') message++;
7154
7155     /*
7156      * [HGM] engine debug message: ignore lines starting with '#' character
7157      */
7158     if(cps->debug && *message == '#') return;
7159
7160     /*
7161      * Look for book output
7162      */
7163     if (cps == &first && bookRequested) {
7164         if (message[0] == '\t' || message[0] == ' ') {
7165             /* Part of the book output is here; append it */
7166             strcat(bookOutput, message);
7167             strcat(bookOutput, "  \n");
7168             return;
7169         } else if (bookOutput[0] != NULLCHAR) {
7170             /* All of book output has arrived; display it */
7171             char *p = bookOutput;
7172             while (*p != NULLCHAR) {
7173                 if (*p == '\t') *p = ' ';
7174                 p++;
7175             }
7176             DisplayInformation(bookOutput);
7177             bookRequested = FALSE;
7178             /* Fall through to parse the current output */
7179         }
7180     }
7181
7182     /*
7183      * Look for machine move.
7184      */
7185     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7186         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7187     {
7188         /* This method is only useful on engines that support ping */
7189         if (cps->lastPing != cps->lastPong) {
7190           if (gameMode == BeginningOfGame) {
7191             /* Extra move from before last new; ignore */
7192             if (appData.debugMode) {
7193                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7194             }
7195           } else {
7196             if (appData.debugMode) {
7197                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7198                         cps->which, gameMode);
7199             }
7200
7201             SendToProgram("undo\n", cps);
7202           }
7203           return;
7204         }
7205
7206         switch (gameMode) {
7207           case BeginningOfGame:
7208             /* Extra move from before last reset; ignore */
7209             if (appData.debugMode) {
7210                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7211             }
7212             return;
7213
7214           case EndOfGame:
7215           case IcsIdle:
7216           default:
7217             /* Extra move after we tried to stop.  The mode test is
7218                not a reliable way of detecting this problem, but it's
7219                the best we can do on engines that don't support ping.
7220             */
7221             if (appData.debugMode) {
7222                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7223                         cps->which, gameMode);
7224             }
7225             SendToProgram("undo\n", cps);
7226             return;
7227
7228           case MachinePlaysWhite:
7229           case IcsPlayingWhite:
7230             machineWhite = TRUE;
7231             break;
7232
7233           case MachinePlaysBlack:
7234           case IcsPlayingBlack:
7235             machineWhite = FALSE;
7236             break;
7237
7238           case TwoMachinesPlay:
7239             machineWhite = (cps->twoMachinesColor[0] == 'w');
7240             break;
7241         }
7242         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7243             if (appData.debugMode) {
7244                 fprintf(debugFP,
7245                         "Ignoring move out of turn by %s, gameMode %d"
7246                         ", forwardMost %d\n",
7247                         cps->which, gameMode, forwardMostMove);
7248             }
7249             return;
7250         }
7251
7252     if (appData.debugMode) { int f = forwardMostMove;
7253         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7254                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7255                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7256     }
7257         if(cps->alphaRank) AlphaRank(machineMove, 4);
7258         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7259                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7260             /* Machine move could not be parsed; ignore it. */
7261           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7262                     machineMove, cps->which);
7263             DisplayError(buf1, 0);
7264             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7265                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7266             if (gameMode == TwoMachinesPlay) {
7267               GameEnds(machineWhite ? BlackWins : WhiteWins,
7268                        buf1, GE_XBOARD);
7269             }
7270             return;
7271         }
7272
7273         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7274         /* So we have to redo legality test with true e.p. status here,  */
7275         /* to make sure an illegal e.p. capture does not slip through,   */
7276         /* to cause a forfeit on a justified illegal-move complaint      */
7277         /* of the opponent.                                              */
7278         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7279            ChessMove moveType;
7280            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7281                              fromY, fromX, toY, toX, promoChar);
7282             if (appData.debugMode) {
7283                 int i;
7284                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7285                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7286                 fprintf(debugFP, "castling rights\n");
7287             }
7288             if(moveType == IllegalMove) {
7289               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7290                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7291                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7292                            buf1, GE_XBOARD);
7293                 return;
7294            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7295            /* [HGM] Kludge to handle engines that send FRC-style castling
7296               when they shouldn't (like TSCP-Gothic) */
7297            switch(moveType) {
7298              case WhiteASideCastleFR:
7299              case BlackASideCastleFR:
7300                toX+=2;
7301                currentMoveString[2]++;
7302                break;
7303              case WhiteHSideCastleFR:
7304              case BlackHSideCastleFR:
7305                toX--;
7306                currentMoveString[2]--;
7307                break;
7308              default: ; // nothing to do, but suppresses warning of pedantic compilers
7309            }
7310         }
7311         hintRequested = FALSE;
7312         lastHint[0] = NULLCHAR;
7313         bookRequested = FALSE;
7314         /* Program may be pondering now */
7315         cps->maybeThinking = TRUE;
7316         if (cps->sendTime == 2) cps->sendTime = 1;
7317         if (cps->offeredDraw) cps->offeredDraw--;
7318
7319         /* currentMoveString is set as a side-effect of ParseOneMove */
7320         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7321         strcat(machineMove, "\n");
7322         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7323
7324         /* [AS] Save move info*/
7325         pvInfoList[ forwardMostMove ].score = programStats.score;
7326         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7327         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7328
7329         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7330
7331         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7332         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7333             int count = 0;
7334
7335             while( count < adjudicateLossPlies ) {
7336                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7337
7338                 if( count & 1 ) {
7339                     score = -score; /* Flip score for winning side */
7340                 }
7341
7342                 if( score > adjudicateLossThreshold ) {
7343                     break;
7344                 }
7345
7346                 count++;
7347             }
7348
7349             if( count >= adjudicateLossPlies ) {
7350                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7351
7352                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7353                     "Xboard adjudication",
7354                     GE_XBOARD );
7355
7356                 return;
7357             }
7358         }
7359
7360         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7361
7362 #if ZIPPY
7363         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7364             first.initDone) {
7365           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7366                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7367                 SendToICS("draw ");
7368                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7369           }
7370           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7371           ics_user_moved = 1;
7372           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7373                 char buf[3*MSG_SIZ];
7374
7375                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7376                         programStats.score / 100.,
7377                         programStats.depth,
7378                         programStats.time / 100.,
7379                         (unsigned int)programStats.nodes,
7380                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7381                         programStats.movelist);
7382                 SendToICS(buf);
7383 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7384           }
7385         }
7386 #endif
7387
7388         /* [AS] Clear stats for next move */
7389         ClearProgramStats();
7390         thinkOutput[0] = NULLCHAR;
7391         hiddenThinkOutputState = 0;
7392
7393         bookHit = NULL;
7394         if (gameMode == TwoMachinesPlay) {
7395             /* [HGM] relaying draw offers moved to after reception of move */
7396             /* and interpreting offer as claim if it brings draw condition */
7397             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7398                 SendToProgram("draw\n", cps->other);
7399             }
7400             if (cps->other->sendTime) {
7401                 SendTimeRemaining(cps->other,
7402                                   cps->other->twoMachinesColor[0] == 'w');
7403             }
7404             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7405             if (firstMove && !bookHit) {
7406                 firstMove = FALSE;
7407                 if (cps->other->useColors) {
7408                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7409                 }
7410                 SendToProgram("go\n", cps->other);
7411             }
7412             cps->other->maybeThinking = TRUE;
7413         }
7414
7415         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7416
7417         if (!pausing && appData.ringBellAfterMoves) {
7418             RingBell();
7419         }
7420
7421         /*
7422          * Reenable menu items that were disabled while
7423          * machine was thinking
7424          */
7425         if (gameMode != TwoMachinesPlay)
7426             SetUserThinkingEnables();
7427
7428         // [HGM] book: after book hit opponent has received move and is now in force mode
7429         // force the book reply into it, and then fake that it outputted this move by jumping
7430         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7431         if(bookHit) {
7432                 static char bookMove[MSG_SIZ]; // a bit generous?
7433
7434                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7435                 strcat(bookMove, bookHit);
7436                 message = bookMove;
7437                 cps = cps->other;
7438                 programStats.nodes = programStats.depth = programStats.time =
7439                 programStats.score = programStats.got_only_move = 0;
7440                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7441
7442                 if(cps->lastPing != cps->lastPong) {
7443                     savedMessage = message; // args for deferred call
7444                     savedState = cps;
7445                     ScheduleDelayedEvent(DeferredBookMove, 10);
7446                     return;
7447                 }
7448                 goto FakeBookMove;
7449         }
7450
7451         return;
7452     }
7453
7454     /* Set special modes for chess engines.  Later something general
7455      *  could be added here; for now there is just one kludge feature,
7456      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7457      *  when "xboard" is given as an interactive command.
7458      */
7459     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7460         cps->useSigint = FALSE;
7461         cps->useSigterm = FALSE;
7462     }
7463     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7464       ParseFeatures(message+8, cps);
7465       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7466     }
7467
7468     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7469       int dummy, s=6; char buf[MSG_SIZ];
7470       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7471       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7472       ParseFEN(boards[0], &dummy, message+s);
7473       DrawPosition(TRUE, boards[0]);
7474       startedFromSetupPosition = TRUE;
7475       return;
7476     }
7477     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7478      * want this, I was asked to put it in, and obliged.
7479      */
7480     if (!strncmp(message, "setboard ", 9)) {
7481         Board initial_position;
7482
7483         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7484
7485         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7486             DisplayError(_("Bad FEN received from engine"), 0);
7487             return ;
7488         } else {
7489            Reset(TRUE, FALSE);
7490            CopyBoard(boards[0], initial_position);
7491            initialRulePlies = FENrulePlies;
7492            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7493            else gameMode = MachinePlaysBlack;
7494            DrawPosition(FALSE, boards[currentMove]);
7495         }
7496         return;
7497     }
7498
7499     /*
7500      * Look for communication commands
7501      */
7502     if (!strncmp(message, "telluser ", 9)) {
7503         if(message[9] == '\\' && message[10] == '\\')
7504             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7505         DisplayNote(message + 9);
7506         return;
7507     }
7508     if (!strncmp(message, "tellusererror ", 14)) {
7509         cps->userError = 1;
7510         if(message[14] == '\\' && message[15] == '\\')
7511             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7512         DisplayError(message + 14, 0);
7513         return;
7514     }
7515     if (!strncmp(message, "tellopponent ", 13)) {
7516       if (appData.icsActive) {
7517         if (loggedOn) {
7518           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7519           SendToICS(buf1);
7520         }
7521       } else {
7522         DisplayNote(message + 13);
7523       }
7524       return;
7525     }
7526     if (!strncmp(message, "tellothers ", 11)) {
7527       if (appData.icsActive) {
7528         if (loggedOn) {
7529           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7530           SendToICS(buf1);
7531         }
7532       }
7533       return;
7534     }
7535     if (!strncmp(message, "tellall ", 8)) {
7536       if (appData.icsActive) {
7537         if (loggedOn) {
7538           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7539           SendToICS(buf1);
7540         }
7541       } else {
7542         DisplayNote(message + 8);
7543       }
7544       return;
7545     }
7546     if (strncmp(message, "warning", 7) == 0) {
7547         /* Undocumented feature, use tellusererror in new code */
7548         DisplayError(message, 0);
7549         return;
7550     }
7551     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7552         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7553         strcat(realname, " query");
7554         AskQuestion(realname, buf2, buf1, cps->pr);
7555         return;
7556     }
7557     /* Commands from the engine directly to ICS.  We don't allow these to be
7558      *  sent until we are logged on. Crafty kibitzes have been known to
7559      *  interfere with the login process.
7560      */
7561     if (loggedOn) {
7562         if (!strncmp(message, "tellics ", 8)) {
7563             SendToICS(message + 8);
7564             SendToICS("\n");
7565             return;
7566         }
7567         if (!strncmp(message, "tellicsnoalias ", 15)) {
7568             SendToICS(ics_prefix);
7569             SendToICS(message + 15);
7570             SendToICS("\n");
7571             return;
7572         }
7573         /* The following are for backward compatibility only */
7574         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7575             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7576             SendToICS(ics_prefix);
7577             SendToICS(message);
7578             SendToICS("\n");
7579             return;
7580         }
7581     }
7582     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7583         return;
7584     }
7585     /*
7586      * If the move is illegal, cancel it and redraw the board.
7587      * Also deal with other error cases.  Matching is rather loose
7588      * here to accommodate engines written before the spec.
7589      */
7590     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7591         strncmp(message, "Error", 5) == 0) {
7592         if (StrStr(message, "name") ||
7593             StrStr(message, "rating") || StrStr(message, "?") ||
7594             StrStr(message, "result") || StrStr(message, "board") ||
7595             StrStr(message, "bk") || StrStr(message, "computer") ||
7596             StrStr(message, "variant") || StrStr(message, "hint") ||
7597             StrStr(message, "random") || StrStr(message, "depth") ||
7598             StrStr(message, "accepted")) {
7599             return;
7600         }
7601         if (StrStr(message, "protover")) {
7602           /* Program is responding to input, so it's apparently done
7603              initializing, and this error message indicates it is
7604              protocol version 1.  So we don't need to wait any longer
7605              for it to initialize and send feature commands. */
7606           FeatureDone(cps, 1);
7607           cps->protocolVersion = 1;
7608           return;
7609         }
7610         cps->maybeThinking = FALSE;
7611
7612         if (StrStr(message, "draw")) {
7613             /* Program doesn't have "draw" command */
7614             cps->sendDrawOffers = 0;
7615             return;
7616         }
7617         if (cps->sendTime != 1 &&
7618             (StrStr(message, "time") || StrStr(message, "otim"))) {
7619           /* Program apparently doesn't have "time" or "otim" command */
7620           cps->sendTime = 0;
7621           return;
7622         }
7623         if (StrStr(message, "analyze")) {
7624             cps->analysisSupport = FALSE;
7625             cps->analyzing = FALSE;
7626             Reset(FALSE, TRUE);
7627             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7628             DisplayError(buf2, 0);
7629             return;
7630         }
7631         if (StrStr(message, "(no matching move)st")) {
7632           /* Special kludge for GNU Chess 4 only */
7633           cps->stKludge = TRUE;
7634           SendTimeControl(cps, movesPerSession, timeControl,
7635                           timeIncrement, appData.searchDepth,
7636                           searchTime);
7637           return;
7638         }
7639         if (StrStr(message, "(no matching move)sd")) {
7640           /* Special kludge for GNU Chess 4 only */
7641           cps->sdKludge = TRUE;
7642           SendTimeControl(cps, movesPerSession, timeControl,
7643                           timeIncrement, appData.searchDepth,
7644                           searchTime);
7645           return;
7646         }
7647         if (!StrStr(message, "llegal")) {
7648             return;
7649         }
7650         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7651             gameMode == IcsIdle) return;
7652         if (forwardMostMove <= backwardMostMove) return;
7653         if (pausing) PauseEvent();
7654       if(appData.forceIllegal) {
7655             // [HGM] illegal: machine refused move; force position after move into it
7656           SendToProgram("force\n", cps);
7657           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7658                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7659                 // when black is to move, while there might be nothing on a2 or black
7660                 // might already have the move. So send the board as if white has the move.
7661                 // But first we must change the stm of the engine, as it refused the last move
7662                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7663                 if(WhiteOnMove(forwardMostMove)) {
7664                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7665                     SendBoard(cps, forwardMostMove); // kludgeless board
7666                 } else {
7667                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7668                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7669                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7670                 }
7671           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7672             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7673                  gameMode == TwoMachinesPlay)
7674               SendToProgram("go\n", cps);
7675             return;
7676       } else
7677         if (gameMode == PlayFromGameFile) {
7678             /* Stop reading this game file */
7679             gameMode = EditGame;
7680             ModeHighlight();
7681         }
7682         currentMove = forwardMostMove-1;
7683         DisplayMove(currentMove-1); /* before DisplayMoveError */
7684         SwitchClocks(forwardMostMove-1); // [HGM] race
7685         DisplayBothClocks();
7686         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7687                 parseList[currentMove], cps->which);
7688         DisplayMoveError(buf1);
7689         DrawPosition(FALSE, boards[currentMove]);
7690
7691         /* [HGM] illegal-move claim should forfeit game when Xboard */
7692         /* only passes fully legal moves                            */
7693         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7694             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7695                                 "False illegal-move claim", GE_XBOARD );
7696         }
7697         return;
7698     }
7699     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7700         /* Program has a broken "time" command that
7701            outputs a string not ending in newline.
7702            Don't use it. */
7703         cps->sendTime = 0;
7704     }
7705
7706     /*
7707      * If chess program startup fails, exit with an error message.
7708      * Attempts to recover here are futile.
7709      */
7710     if ((StrStr(message, "unknown host") != NULL)
7711         || (StrStr(message, "No remote directory") != NULL)
7712         || (StrStr(message, "not found") != NULL)
7713         || (StrStr(message, "No such file") != NULL)
7714         || (StrStr(message, "can't alloc") != NULL)
7715         || (StrStr(message, "Permission denied") != NULL)) {
7716
7717         cps->maybeThinking = FALSE;
7718         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7719                 cps->which, cps->program, cps->host, message);
7720         RemoveInputSource(cps->isr);
7721         DisplayFatalError(buf1, 0, 1);
7722         return;
7723     }
7724
7725     /*
7726      * Look for hint output
7727      */
7728     if (sscanf(message, "Hint: %s", buf1) == 1) {
7729         if (cps == &first && hintRequested) {
7730             hintRequested = FALSE;
7731             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7732                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7733                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7734                                     PosFlags(forwardMostMove),
7735                                     fromY, fromX, toY, toX, promoChar, buf1);
7736                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7737                 DisplayInformation(buf2);
7738             } else {
7739                 /* Hint move could not be parsed!? */
7740               snprintf(buf2, sizeof(buf2),
7741                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7742                         buf1, cps->which);
7743                 DisplayError(buf2, 0);
7744             }
7745         } else {
7746           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7747         }
7748         return;
7749     }
7750
7751     /*
7752      * Ignore other messages if game is not in progress
7753      */
7754     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7755         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7756
7757     /*
7758      * look for win, lose, draw, or draw offer
7759      */
7760     if (strncmp(message, "1-0", 3) == 0) {
7761         char *p, *q, *r = "";
7762         p = strchr(message, '{');
7763         if (p) {
7764             q = strchr(p, '}');
7765             if (q) {
7766                 *q = NULLCHAR;
7767                 r = p + 1;
7768             }
7769         }
7770         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7771         return;
7772     } else if (strncmp(message, "0-1", 3) == 0) {
7773         char *p, *q, *r = "";
7774         p = strchr(message, '{');
7775         if (p) {
7776             q = strchr(p, '}');
7777             if (q) {
7778                 *q = NULLCHAR;
7779                 r = p + 1;
7780             }
7781         }
7782         /* Kludge for Arasan 4.1 bug */
7783         if (strcmp(r, "Black resigns") == 0) {
7784             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7785             return;
7786         }
7787         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7788         return;
7789     } else if (strncmp(message, "1/2", 3) == 0) {
7790         char *p, *q, *r = "";
7791         p = strchr(message, '{');
7792         if (p) {
7793             q = strchr(p, '}');
7794             if (q) {
7795                 *q = NULLCHAR;
7796                 r = p + 1;
7797             }
7798         }
7799
7800         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7801         return;
7802
7803     } else if (strncmp(message, "White resign", 12) == 0) {
7804         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7805         return;
7806     } else if (strncmp(message, "Black resign", 12) == 0) {
7807         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7808         return;
7809     } else if (strncmp(message, "White matches", 13) == 0 ||
7810                strncmp(message, "Black matches", 13) == 0   ) {
7811         /* [HGM] ignore GNUShogi noises */
7812         return;
7813     } else if (strncmp(message, "White", 5) == 0 &&
7814                message[5] != '(' &&
7815                StrStr(message, "Black") == NULL) {
7816         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7817         return;
7818     } else if (strncmp(message, "Black", 5) == 0 &&
7819                message[5] != '(') {
7820         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7821         return;
7822     } else if (strcmp(message, "resign") == 0 ||
7823                strcmp(message, "computer resigns") == 0) {
7824         switch (gameMode) {
7825           case MachinePlaysBlack:
7826           case IcsPlayingBlack:
7827             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7828             break;
7829           case MachinePlaysWhite:
7830           case IcsPlayingWhite:
7831             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7832             break;
7833           case TwoMachinesPlay:
7834             if (cps->twoMachinesColor[0] == 'w')
7835               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7836             else
7837               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7838             break;
7839           default:
7840             /* can't happen */
7841             break;
7842         }
7843         return;
7844     } else if (strncmp(message, "opponent mates", 14) == 0) {
7845         switch (gameMode) {
7846           case MachinePlaysBlack:
7847           case IcsPlayingBlack:
7848             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7849             break;
7850           case MachinePlaysWhite:
7851           case IcsPlayingWhite:
7852             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7853             break;
7854           case TwoMachinesPlay:
7855             if (cps->twoMachinesColor[0] == 'w')
7856               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7857             else
7858               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7859             break;
7860           default:
7861             /* can't happen */
7862             break;
7863         }
7864         return;
7865     } else if (strncmp(message, "computer mates", 14) == 0) {
7866         switch (gameMode) {
7867           case MachinePlaysBlack:
7868           case IcsPlayingBlack:
7869             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7870             break;
7871           case MachinePlaysWhite:
7872           case IcsPlayingWhite:
7873             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7874             break;
7875           case TwoMachinesPlay:
7876             if (cps->twoMachinesColor[0] == 'w')
7877               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7878             else
7879               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7880             break;
7881           default:
7882             /* can't happen */
7883             break;
7884         }
7885         return;
7886     } else if (strncmp(message, "checkmate", 9) == 0) {
7887         if (WhiteOnMove(forwardMostMove)) {
7888             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7889         } else {
7890             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7891         }
7892         return;
7893     } else if (strstr(message, "Draw") != NULL ||
7894                strstr(message, "game is a draw") != NULL) {
7895         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7896         return;
7897     } else if (strstr(message, "offer") != NULL &&
7898                strstr(message, "draw") != NULL) {
7899 #if ZIPPY
7900         if (appData.zippyPlay && first.initDone) {
7901             /* Relay offer to ICS */
7902             SendToICS(ics_prefix);
7903             SendToICS("draw\n");
7904         }
7905 #endif
7906         cps->offeredDraw = 2; /* valid until this engine moves twice */
7907         if (gameMode == TwoMachinesPlay) {
7908             if (cps->other->offeredDraw) {
7909                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7910             /* [HGM] in two-machine mode we delay relaying draw offer      */
7911             /* until after we also have move, to see if it is really claim */
7912             }
7913         } else if (gameMode == MachinePlaysWhite ||
7914                    gameMode == MachinePlaysBlack) {
7915           if (userOfferedDraw) {
7916             DisplayInformation(_("Machine accepts your draw offer"));
7917             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7918           } else {
7919             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7920           }
7921         }
7922     }
7923
7924
7925     /*
7926      * Look for thinking output
7927      */
7928     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7929           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7930                                 ) {
7931         int plylev, mvleft, mvtot, curscore, time;
7932         char mvname[MOVE_LEN];
7933         u64 nodes; // [DM]
7934         char plyext;
7935         int ignore = FALSE;
7936         int prefixHint = FALSE;
7937         mvname[0] = NULLCHAR;
7938
7939         switch (gameMode) {
7940           case MachinePlaysBlack:
7941           case IcsPlayingBlack:
7942             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7943             break;
7944           case MachinePlaysWhite:
7945           case IcsPlayingWhite:
7946             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7947             break;
7948           case AnalyzeMode:
7949           case AnalyzeFile:
7950             break;
7951           case IcsObserving: /* [DM] icsEngineAnalyze */
7952             if (!appData.icsEngineAnalyze) ignore = TRUE;
7953             break;
7954           case TwoMachinesPlay:
7955             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7956                 ignore = TRUE;
7957             }
7958             break;
7959           default:
7960             ignore = TRUE;
7961             break;
7962         }
7963
7964         if (!ignore) {
7965             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7966             buf1[0] = NULLCHAR;
7967             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7968                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7969
7970                 if (plyext != ' ' && plyext != '\t') {
7971                     time *= 100;
7972                 }
7973
7974                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7975                 if( cps->scoreIsAbsolute &&
7976                     ( gameMode == MachinePlaysBlack ||
7977                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7978                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7979                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7980                      !WhiteOnMove(currentMove)
7981                     ) )
7982                 {
7983                     curscore = -curscore;
7984                 }
7985
7986
7987                 tempStats.depth = plylev;
7988                 tempStats.nodes = nodes;
7989                 tempStats.time = time;
7990                 tempStats.score = curscore;
7991                 tempStats.got_only_move = 0;
7992
7993                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7994                         int ticklen;
7995
7996                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7997                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7998                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7999                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8000                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8001                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8002                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8003                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8004                 }
8005
8006                 /* Buffer overflow protection */
8007                 if (buf1[0] != NULLCHAR) {
8008                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8009                         && appData.debugMode) {
8010                         fprintf(debugFP,
8011                                 "PV is too long; using the first %u bytes.\n",
8012                                 (unsigned) sizeof(tempStats.movelist) - 1);
8013                     }
8014
8015                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8016                 } else {
8017                     sprintf(tempStats.movelist, " no PV\n");
8018                 }
8019
8020                 if (tempStats.seen_stat) {
8021                     tempStats.ok_to_send = 1;
8022                 }
8023
8024                 if (strchr(tempStats.movelist, '(') != NULL) {
8025                     tempStats.line_is_book = 1;
8026                     tempStats.nr_moves = 0;
8027                     tempStats.moves_left = 0;
8028                 } else {
8029                     tempStats.line_is_book = 0;
8030                 }
8031
8032                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8033                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8034
8035                 SendProgramStatsToFrontend( cps, &tempStats );
8036
8037                 /*
8038                     [AS] Protect the thinkOutput buffer from overflow... this
8039                     is only useful if buf1 hasn't overflowed first!
8040                 */
8041                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8042                          plylev,
8043                          (gameMode == TwoMachinesPlay ?
8044                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8045                          ((double) curscore) / 100.0,
8046                          prefixHint ? lastHint : "",
8047                          prefixHint ? " " : "" );
8048
8049                 if( buf1[0] != NULLCHAR ) {
8050                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8051
8052                     if( strlen(buf1) > max_len ) {
8053                         if( appData.debugMode) {
8054                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8055                         }
8056                         buf1[max_len+1] = '\0';
8057                     }
8058
8059                     strcat( thinkOutput, buf1 );
8060                 }
8061
8062                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8063                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8064                     DisplayMove(currentMove - 1);
8065                 }
8066                 return;
8067
8068             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8069                 /* crafty (9.25+) says "(only move) <move>"
8070                  * if there is only 1 legal move
8071                  */
8072                 sscanf(p, "(only move) %s", buf1);
8073                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8074                 sprintf(programStats.movelist, "%s (only move)", buf1);
8075                 programStats.depth = 1;
8076                 programStats.nr_moves = 1;
8077                 programStats.moves_left = 1;
8078                 programStats.nodes = 1;
8079                 programStats.time = 1;
8080                 programStats.got_only_move = 1;
8081
8082                 /* Not really, but we also use this member to
8083                    mean "line isn't going to change" (Crafty
8084                    isn't searching, so stats won't change) */
8085                 programStats.line_is_book = 1;
8086
8087                 SendProgramStatsToFrontend( cps, &programStats );
8088
8089                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8090                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8091                     DisplayMove(currentMove - 1);
8092                 }
8093                 return;
8094             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8095                               &time, &nodes, &plylev, &mvleft,
8096                               &mvtot, mvname) >= 5) {
8097                 /* The stat01: line is from Crafty (9.29+) in response
8098                    to the "." command */
8099                 programStats.seen_stat = 1;
8100                 cps->maybeThinking = TRUE;
8101
8102                 if (programStats.got_only_move || !appData.periodicUpdates)
8103                   return;
8104
8105                 programStats.depth = plylev;
8106                 programStats.time = time;
8107                 programStats.nodes = nodes;
8108                 programStats.moves_left = mvleft;
8109                 programStats.nr_moves = mvtot;
8110                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8111                 programStats.ok_to_send = 1;
8112                 programStats.movelist[0] = '\0';
8113
8114                 SendProgramStatsToFrontend( cps, &programStats );
8115
8116                 return;
8117
8118             } else if (strncmp(message,"++",2) == 0) {
8119                 /* Crafty 9.29+ outputs this */
8120                 programStats.got_fail = 2;
8121                 return;
8122
8123             } else if (strncmp(message,"--",2) == 0) {
8124                 /* Crafty 9.29+ outputs this */
8125                 programStats.got_fail = 1;
8126                 return;
8127
8128             } else if (thinkOutput[0] != NULLCHAR &&
8129                        strncmp(message, "    ", 4) == 0) {
8130                 unsigned message_len;
8131
8132                 p = message;
8133                 while (*p && *p == ' ') p++;
8134
8135                 message_len = strlen( p );
8136
8137                 /* [AS] Avoid buffer overflow */
8138                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8139                     strcat(thinkOutput, " ");
8140                     strcat(thinkOutput, p);
8141                 }
8142
8143                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8144                     strcat(programStats.movelist, " ");
8145                     strcat(programStats.movelist, p);
8146                 }
8147
8148                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8149                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8150                     DisplayMove(currentMove - 1);
8151                 }
8152                 return;
8153             }
8154         }
8155         else {
8156             buf1[0] = NULLCHAR;
8157
8158             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8159                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8160             {
8161                 ChessProgramStats cpstats;
8162
8163                 if (plyext != ' ' && plyext != '\t') {
8164                     time *= 100;
8165                 }
8166
8167                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8168                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8169                     curscore = -curscore;
8170                 }
8171
8172                 cpstats.depth = plylev;
8173                 cpstats.nodes = nodes;
8174                 cpstats.time = time;
8175                 cpstats.score = curscore;
8176                 cpstats.got_only_move = 0;
8177                 cpstats.movelist[0] = '\0';
8178
8179                 if (buf1[0] != NULLCHAR) {
8180                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8181                 }
8182
8183                 cpstats.ok_to_send = 0;
8184                 cpstats.line_is_book = 0;
8185                 cpstats.nr_moves = 0;
8186                 cpstats.moves_left = 0;
8187
8188                 SendProgramStatsToFrontend( cps, &cpstats );
8189             }
8190         }
8191     }
8192 }
8193
8194
8195 /* Parse a game score from the character string "game", and
8196    record it as the history of the current game.  The game
8197    score is NOT assumed to start from the standard position.
8198    The display is not updated in any way.
8199    */
8200 void
8201 ParseGameHistory(game)
8202      char *game;
8203 {
8204     ChessMove moveType;
8205     int fromX, fromY, toX, toY, boardIndex;
8206     char promoChar;
8207     char *p, *q;
8208     char buf[MSG_SIZ];
8209
8210     if (appData.debugMode)
8211       fprintf(debugFP, "Parsing game history: %s\n", game);
8212
8213     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8214     gameInfo.site = StrSave(appData.icsHost);
8215     gameInfo.date = PGNDate();
8216     gameInfo.round = StrSave("-");
8217
8218     /* Parse out names of players */
8219     while (*game == ' ') game++;
8220     p = buf;
8221     while (*game != ' ') *p++ = *game++;
8222     *p = NULLCHAR;
8223     gameInfo.white = StrSave(buf);
8224     while (*game == ' ') game++;
8225     p = buf;
8226     while (*game != ' ' && *game != '\n') *p++ = *game++;
8227     *p = NULLCHAR;
8228     gameInfo.black = StrSave(buf);
8229
8230     /* Parse moves */
8231     boardIndex = blackPlaysFirst ? 1 : 0;
8232     yynewstr(game);
8233     for (;;) {
8234         yyboardindex = boardIndex;
8235         moveType = (ChessMove) Myylex();
8236         switch (moveType) {
8237           case IllegalMove:             /* maybe suicide chess, etc. */
8238   if (appData.debugMode) {
8239     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8240     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8241     setbuf(debugFP, NULL);
8242   }
8243           case WhitePromotion:
8244           case BlackPromotion:
8245           case WhiteNonPromotion:
8246           case BlackNonPromotion:
8247           case NormalMove:
8248           case WhiteCapturesEnPassant:
8249           case BlackCapturesEnPassant:
8250           case WhiteKingSideCastle:
8251           case WhiteQueenSideCastle:
8252           case BlackKingSideCastle:
8253           case BlackQueenSideCastle:
8254           case WhiteKingSideCastleWild:
8255           case WhiteQueenSideCastleWild:
8256           case BlackKingSideCastleWild:
8257           case BlackQueenSideCastleWild:
8258           /* PUSH Fabien */
8259           case WhiteHSideCastleFR:
8260           case WhiteASideCastleFR:
8261           case BlackHSideCastleFR:
8262           case BlackASideCastleFR:
8263           /* POP Fabien */
8264             fromX = currentMoveString[0] - AAA;
8265             fromY = currentMoveString[1] - ONE;
8266             toX = currentMoveString[2] - AAA;
8267             toY = currentMoveString[3] - ONE;
8268             promoChar = currentMoveString[4];
8269             break;
8270           case WhiteDrop:
8271           case BlackDrop:
8272             fromX = moveType == WhiteDrop ?
8273               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8274             (int) CharToPiece(ToLower(currentMoveString[0]));
8275             fromY = DROP_RANK;
8276             toX = currentMoveString[2] - AAA;
8277             toY = currentMoveString[3] - ONE;
8278             promoChar = NULLCHAR;
8279             break;
8280           case AmbiguousMove:
8281             /* bug? */
8282             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8283   if (appData.debugMode) {
8284     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8285     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8286     setbuf(debugFP, NULL);
8287   }
8288             DisplayError(buf, 0);
8289             return;
8290           case ImpossibleMove:
8291             /* bug? */
8292             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8293   if (appData.debugMode) {
8294     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8295     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8296     setbuf(debugFP, NULL);
8297   }
8298             DisplayError(buf, 0);
8299             return;
8300           case EndOfFile:
8301             if (boardIndex < backwardMostMove) {
8302                 /* Oops, gap.  How did that happen? */
8303                 DisplayError(_("Gap in move list"), 0);
8304                 return;
8305             }
8306             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8307             if (boardIndex > forwardMostMove) {
8308                 forwardMostMove = boardIndex;
8309             }
8310             return;
8311           case ElapsedTime:
8312             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8313                 strcat(parseList[boardIndex-1], " ");
8314                 strcat(parseList[boardIndex-1], yy_text);
8315             }
8316             continue;
8317           case Comment:
8318           case PGNTag:
8319           case NAG:
8320           default:
8321             /* ignore */
8322             continue;
8323           case WhiteWins:
8324           case BlackWins:
8325           case GameIsDrawn:
8326           case GameUnfinished:
8327             if (gameMode == IcsExamining) {
8328                 if (boardIndex < backwardMostMove) {
8329                     /* Oops, gap.  How did that happen? */
8330                     return;
8331                 }
8332                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8333                 return;
8334             }
8335             gameInfo.result = moveType;
8336             p = strchr(yy_text, '{');
8337             if (p == NULL) p = strchr(yy_text, '(');
8338             if (p == NULL) {
8339                 p = yy_text;
8340                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8341             } else {
8342                 q = strchr(p, *p == '{' ? '}' : ')');
8343                 if (q != NULL) *q = NULLCHAR;
8344                 p++;
8345             }
8346             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8347             gameInfo.resultDetails = StrSave(p);
8348             continue;
8349         }
8350         if (boardIndex >= forwardMostMove &&
8351             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8352             backwardMostMove = blackPlaysFirst ? 1 : 0;
8353             return;
8354         }
8355         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8356                                  fromY, fromX, toY, toX, promoChar,
8357                                  parseList[boardIndex]);
8358         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8359         /* currentMoveString is set as a side-effect of yylex */
8360         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8361         strcat(moveList[boardIndex], "\n");
8362         boardIndex++;
8363         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8364         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8365           case MT_NONE:
8366           case MT_STALEMATE:
8367           default:
8368             break;
8369           case MT_CHECK:
8370             if(gameInfo.variant != VariantShogi)
8371                 strcat(parseList[boardIndex - 1], "+");
8372             break;
8373           case MT_CHECKMATE:
8374           case MT_STAINMATE:
8375             strcat(parseList[boardIndex - 1], "#");
8376             break;
8377         }
8378     }
8379 }
8380
8381
8382 /* Apply a move to the given board  */
8383 void
8384 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8385      int fromX, fromY, toX, toY;
8386      int promoChar;
8387      Board board;
8388 {
8389   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8390   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8391
8392     /* [HGM] compute & store e.p. status and castling rights for new position */
8393     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8394
8395       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8396       oldEP = (signed char)board[EP_STATUS];
8397       board[EP_STATUS] = EP_NONE;
8398
8399       if( board[toY][toX] != EmptySquare )
8400            board[EP_STATUS] = EP_CAPTURE;
8401
8402   if (fromY == DROP_RANK) {
8403         /* must be first */
8404         piece = board[toY][toX] = (ChessSquare) fromX;
8405   } else {
8406       int i;
8407
8408       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8409            if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8410       } else
8411       if( board[fromY][fromX] == WhitePawn ) {
8412            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8413                board[EP_STATUS] = EP_PAWN_MOVE;
8414            if( toY-fromY==2) {
8415                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8416                         gameInfo.variant != VariantBerolina || toX < fromX)
8417                       board[EP_STATUS] = toX | berolina;
8418                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8419                         gameInfo.variant != VariantBerolina || toX > fromX)
8420                       board[EP_STATUS] = toX;
8421            }
8422       } else
8423       if( board[fromY][fromX] == BlackPawn ) {
8424            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8425                board[EP_STATUS] = EP_PAWN_MOVE;
8426            if( toY-fromY== -2) {
8427                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8428                         gameInfo.variant != VariantBerolina || toX < fromX)
8429                       board[EP_STATUS] = toX | berolina;
8430                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8431                         gameInfo.variant != VariantBerolina || toX > fromX)
8432                       board[EP_STATUS] = toX;
8433            }
8434        }
8435
8436        for(i=0; i<nrCastlingRights; i++) {
8437            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8438               board[CASTLING][i] == toX   && castlingRank[i] == toY
8439              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8440        }
8441
8442      if (fromX == toX && fromY == toY) return;
8443
8444      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8445      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8446      if(gameInfo.variant == VariantKnightmate)
8447          king += (int) WhiteUnicorn - (int) WhiteKing;
8448
8449     /* Code added by Tord: */
8450     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8451     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8452         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8453       board[fromY][fromX] = EmptySquare;
8454       board[toY][toX] = EmptySquare;
8455       if((toX > fromX) != (piece == WhiteRook)) {
8456         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8457       } else {
8458         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8459       }
8460     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8461                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8462       board[fromY][fromX] = EmptySquare;
8463       board[toY][toX] = EmptySquare;
8464       if((toX > fromX) != (piece == BlackRook)) {
8465         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8466       } else {
8467         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8468       }
8469     /* End of code added by Tord */
8470
8471     } else if (board[fromY][fromX] == king
8472         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8473         && toY == fromY && toX > fromX+1) {
8474         board[fromY][fromX] = EmptySquare;
8475         board[toY][toX] = king;
8476         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8477         board[fromY][BOARD_RGHT-1] = EmptySquare;
8478     } else if (board[fromY][fromX] == king
8479         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8480                && toY == fromY && toX < fromX-1) {
8481         board[fromY][fromX] = EmptySquare;
8482         board[toY][toX] = king;
8483         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8484         board[fromY][BOARD_LEFT] = EmptySquare;
8485     } else if (board[fromY][fromX] == WhitePawn
8486                && toY >= BOARD_HEIGHT-promoRank
8487                && gameInfo.variant != VariantXiangqi
8488                ) {
8489         /* white pawn promotion */
8490         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8491         if (board[toY][toX] == EmptySquare) {
8492             board[toY][toX] = WhiteQueen;
8493         }
8494         if(gameInfo.variant==VariantBughouse ||
8495            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8496             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8497         board[fromY][fromX] = EmptySquare;
8498     } else if ((fromY == BOARD_HEIGHT-4)
8499                && (toX != fromX)
8500                && gameInfo.variant != VariantXiangqi
8501                && gameInfo.variant != VariantBerolina
8502                && (board[fromY][fromX] == WhitePawn)
8503                && (board[toY][toX] == EmptySquare)) {
8504         board[fromY][fromX] = EmptySquare;
8505         board[toY][toX] = WhitePawn;
8506         captured = board[toY - 1][toX];
8507         board[toY - 1][toX] = EmptySquare;
8508     } else if ((fromY == BOARD_HEIGHT-4)
8509                && (toX == fromX)
8510                && gameInfo.variant == VariantBerolina
8511                && (board[fromY][fromX] == WhitePawn)
8512                && (board[toY][toX] == EmptySquare)) {
8513         board[fromY][fromX] = EmptySquare;
8514         board[toY][toX] = WhitePawn;
8515         if(oldEP & EP_BEROLIN_A) {
8516                 captured = board[fromY][fromX-1];
8517                 board[fromY][fromX-1] = EmptySquare;
8518         }else{  captured = board[fromY][fromX+1];
8519                 board[fromY][fromX+1] = EmptySquare;
8520         }
8521     } else if (board[fromY][fromX] == king
8522         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8523                && toY == fromY && toX > fromX+1) {
8524         board[fromY][fromX] = EmptySquare;
8525         board[toY][toX] = king;
8526         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8527         board[fromY][BOARD_RGHT-1] = EmptySquare;
8528     } else if (board[fromY][fromX] == king
8529         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8530                && toY == fromY && toX < fromX-1) {
8531         board[fromY][fromX] = EmptySquare;
8532         board[toY][toX] = king;
8533         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8534         board[fromY][BOARD_LEFT] = EmptySquare;
8535     } else if (fromY == 7 && fromX == 3
8536                && board[fromY][fromX] == BlackKing
8537                && toY == 7 && toX == 5) {
8538         board[fromY][fromX] = EmptySquare;
8539         board[toY][toX] = BlackKing;
8540         board[fromY][7] = EmptySquare;
8541         board[toY][4] = BlackRook;
8542     } else if (fromY == 7 && fromX == 3
8543                && board[fromY][fromX] == BlackKing
8544                && toY == 7 && toX == 1) {
8545         board[fromY][fromX] = EmptySquare;
8546         board[toY][toX] = BlackKing;
8547         board[fromY][0] = EmptySquare;
8548         board[toY][2] = BlackRook;
8549     } else if (board[fromY][fromX] == BlackPawn
8550                && toY < promoRank
8551                && gameInfo.variant != VariantXiangqi
8552                ) {
8553         /* black pawn promotion */
8554         board[toY][toX] = CharToPiece(ToLower(promoChar));
8555         if (board[toY][toX] == EmptySquare) {
8556             board[toY][toX] = BlackQueen;
8557         }
8558         if(gameInfo.variant==VariantBughouse ||
8559            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8560             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8561         board[fromY][fromX] = EmptySquare;
8562     } else if ((fromY == 3)
8563                && (toX != fromX)
8564                && gameInfo.variant != VariantXiangqi
8565                && gameInfo.variant != VariantBerolina
8566                && (board[fromY][fromX] == BlackPawn)
8567                && (board[toY][toX] == EmptySquare)) {
8568         board[fromY][fromX] = EmptySquare;
8569         board[toY][toX] = BlackPawn;
8570         captured = board[toY + 1][toX];
8571         board[toY + 1][toX] = EmptySquare;
8572     } else if ((fromY == 3)
8573                && (toX == fromX)
8574                && gameInfo.variant == VariantBerolina
8575                && (board[fromY][fromX] == BlackPawn)
8576                && (board[toY][toX] == EmptySquare)) {
8577         board[fromY][fromX] = EmptySquare;
8578         board[toY][toX] = BlackPawn;
8579         if(oldEP & EP_BEROLIN_A) {
8580                 captured = board[fromY][fromX-1];
8581                 board[fromY][fromX-1] = EmptySquare;
8582         }else{  captured = board[fromY][fromX+1];
8583                 board[fromY][fromX+1] = EmptySquare;
8584         }
8585     } else {
8586         board[toY][toX] = board[fromY][fromX];
8587         board[fromY][fromX] = EmptySquare;
8588     }
8589   }
8590
8591     if (gameInfo.holdingsWidth != 0) {
8592
8593       /* !!A lot more code needs to be written to support holdings  */
8594       /* [HGM] OK, so I have written it. Holdings are stored in the */
8595       /* penultimate board files, so they are automaticlly stored   */
8596       /* in the game history.                                       */
8597       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8598                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8599         /* Delete from holdings, by decreasing count */
8600         /* and erasing image if necessary            */
8601         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8602         if(p < (int) BlackPawn) { /* white drop */
8603              p -= (int)WhitePawn;
8604                  p = PieceToNumber((ChessSquare)p);
8605              if(p >= gameInfo.holdingsSize) p = 0;
8606              if(--board[p][BOARD_WIDTH-2] <= 0)
8607                   board[p][BOARD_WIDTH-1] = EmptySquare;
8608              if((int)board[p][BOARD_WIDTH-2] < 0)
8609                         board[p][BOARD_WIDTH-2] = 0;
8610         } else {                  /* black drop */
8611              p -= (int)BlackPawn;
8612                  p = PieceToNumber((ChessSquare)p);
8613              if(p >= gameInfo.holdingsSize) p = 0;
8614              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8615                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8616              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8617                         board[BOARD_HEIGHT-1-p][1] = 0;
8618         }
8619       }
8620       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8621           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8622         /* [HGM] holdings: Add to holdings, if holdings exist */
8623         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8624                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8625                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8626         }
8627         p = (int) captured;
8628         if (p >= (int) BlackPawn) {
8629           p -= (int)BlackPawn;
8630           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8631                   /* in Shogi restore piece to its original  first */
8632                   captured = (ChessSquare) (DEMOTED captured);
8633                   p = DEMOTED p;
8634           }
8635           p = PieceToNumber((ChessSquare)p);
8636           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8637           board[p][BOARD_WIDTH-2]++;
8638           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8639         } else {
8640           p -= (int)WhitePawn;
8641           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8642                   captured = (ChessSquare) (DEMOTED captured);
8643                   p = DEMOTED p;
8644           }
8645           p = PieceToNumber((ChessSquare)p);
8646           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8647           board[BOARD_HEIGHT-1-p][1]++;
8648           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8649         }
8650       }
8651     } else if (gameInfo.variant == VariantAtomic) {
8652       if (captured != EmptySquare) {
8653         int y, x;
8654         for (y = toY-1; y <= toY+1; y++) {
8655           for (x = toX-1; x <= toX+1; x++) {
8656             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8657                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8658               board[y][x] = EmptySquare;
8659             }
8660           }
8661         }
8662         board[toY][toX] = EmptySquare;
8663       }
8664     }
8665     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8666         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8667     } else
8668     if(promoChar == '+') {
8669         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8670         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8671     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8672         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8673     }
8674     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8675                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8676         // [HGM] superchess: take promotion piece out of holdings
8677         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8678         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8679             if(!--board[k][BOARD_WIDTH-2])
8680                 board[k][BOARD_WIDTH-1] = EmptySquare;
8681         } else {
8682             if(!--board[BOARD_HEIGHT-1-k][1])
8683                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8684         }
8685     }
8686
8687 }
8688
8689 /* Updates forwardMostMove */
8690 void
8691 MakeMove(fromX, fromY, toX, toY, promoChar)
8692      int fromX, fromY, toX, toY;
8693      int promoChar;
8694 {
8695 //    forwardMostMove++; // [HGM] bare: moved downstream
8696
8697     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8698         int timeLeft; static int lastLoadFlag=0; int king, piece;
8699         piece = boards[forwardMostMove][fromY][fromX];
8700         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8701         if(gameInfo.variant == VariantKnightmate)
8702             king += (int) WhiteUnicorn - (int) WhiteKing;
8703         if(forwardMostMove == 0) {
8704             if(blackPlaysFirst)
8705                 fprintf(serverMoves, "%s;", second.tidy);
8706             fprintf(serverMoves, "%s;", first.tidy);
8707             if(!blackPlaysFirst)
8708                 fprintf(serverMoves, "%s;", second.tidy);
8709         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8710         lastLoadFlag = loadFlag;
8711         // print base move
8712         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8713         // print castling suffix
8714         if( toY == fromY && piece == king ) {
8715             if(toX-fromX > 1)
8716                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8717             if(fromX-toX >1)
8718                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8719         }
8720         // e.p. suffix
8721         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8722              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8723              boards[forwardMostMove][toY][toX] == EmptySquare
8724              && fromX != toX && fromY != toY)
8725                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8726         // promotion suffix
8727         if(promoChar != NULLCHAR)
8728                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8729         if(!loadFlag) {
8730             fprintf(serverMoves, "/%d/%d",
8731                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8732             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8733             else                      timeLeft = blackTimeRemaining/1000;
8734             fprintf(serverMoves, "/%d", timeLeft);
8735         }
8736         fflush(serverMoves);
8737     }
8738
8739     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8740       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8741                         0, 1);
8742       return;
8743     }
8744     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8745     if (commentList[forwardMostMove+1] != NULL) {
8746         free(commentList[forwardMostMove+1]);
8747         commentList[forwardMostMove+1] = NULL;
8748     }
8749     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8750     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8751     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8752     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8753     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8754     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8755     gameInfo.result = GameUnfinished;
8756     if (gameInfo.resultDetails != NULL) {
8757         free(gameInfo.resultDetails);
8758         gameInfo.resultDetails = NULL;
8759     }
8760     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8761                               moveList[forwardMostMove - 1]);
8762     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8763                              PosFlags(forwardMostMove - 1),
8764                              fromY, fromX, toY, toX, promoChar,
8765                              parseList[forwardMostMove - 1]);
8766     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8767       case MT_NONE:
8768       case MT_STALEMATE:
8769       default:
8770         break;
8771       case MT_CHECK:
8772         if(gameInfo.variant != VariantShogi)
8773             strcat(parseList[forwardMostMove - 1], "+");
8774         break;
8775       case MT_CHECKMATE:
8776       case MT_STAINMATE:
8777         strcat(parseList[forwardMostMove - 1], "#");
8778         break;
8779     }
8780     if (appData.debugMode) {
8781         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8782     }
8783
8784 }
8785
8786 /* Updates currentMove if not pausing */
8787 void
8788 ShowMove(fromX, fromY, toX, toY)
8789 {
8790     int instant = (gameMode == PlayFromGameFile) ?
8791         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8792     if(appData.noGUI) return;
8793     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8794         if (!instant) {
8795             if (forwardMostMove == currentMove + 1) {
8796                 AnimateMove(boards[forwardMostMove - 1],
8797                             fromX, fromY, toX, toY);
8798             }
8799             if (appData.highlightLastMove) {
8800                 SetHighlights(fromX, fromY, toX, toY);
8801             }
8802         }
8803         currentMove = forwardMostMove;
8804     }
8805
8806     if (instant) return;
8807
8808     DisplayMove(currentMove - 1);
8809     DrawPosition(FALSE, boards[currentMove]);
8810     DisplayBothClocks();
8811     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8812 }
8813
8814 void SendEgtPath(ChessProgramState *cps)
8815 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8816         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8817
8818         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8819
8820         while(*p) {
8821             char c, *q = name+1, *r, *s;
8822
8823             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8824             while(*p && *p != ',') *q++ = *p++;
8825             *q++ = ':'; *q = 0;
8826             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8827                 strcmp(name, ",nalimov:") == 0 ) {
8828                 // take nalimov path from the menu-changeable option first, if it is defined
8829               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8830                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8831             } else
8832             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8833                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8834                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8835                 s = r = StrStr(s, ":") + 1; // beginning of path info
8836                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8837                 c = *r; *r = 0;             // temporarily null-terminate path info
8838                     *--q = 0;               // strip of trailig ':' from name
8839                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8840                 *r = c;
8841                 SendToProgram(buf,cps);     // send egtbpath command for this format
8842             }
8843             if(*p == ',') p++; // read away comma to position for next format name
8844         }
8845 }
8846
8847 void
8848 InitChessProgram(cps, setup)
8849      ChessProgramState *cps;
8850      int setup; /* [HGM] needed to setup FRC opening position */
8851 {
8852     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8853     if (appData.noChessProgram) return;
8854     hintRequested = FALSE;
8855     bookRequested = FALSE;
8856
8857     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8858     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8859     if(cps->memSize) { /* [HGM] memory */
8860       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8861         SendToProgram(buf, cps);
8862     }
8863     SendEgtPath(cps); /* [HGM] EGT */
8864     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8865       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8866         SendToProgram(buf, cps);
8867     }
8868
8869     SendToProgram(cps->initString, cps);
8870     if (gameInfo.variant != VariantNormal &&
8871         gameInfo.variant != VariantLoadable
8872         /* [HGM] also send variant if board size non-standard */
8873         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8874                                             ) {
8875       char *v = VariantName(gameInfo.variant);
8876       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8877         /* [HGM] in protocol 1 we have to assume all variants valid */
8878         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8879         DisplayFatalError(buf, 0, 1);
8880         return;
8881       }
8882
8883       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8884       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8885       if( gameInfo.variant == VariantXiangqi )
8886            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8887       if( gameInfo.variant == VariantShogi )
8888            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8889       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8890            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8891       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8892                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8893            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8894       if( gameInfo.variant == VariantCourier )
8895            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8896       if( gameInfo.variant == VariantSuper )
8897            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8898       if( gameInfo.variant == VariantGreat )
8899            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8900       if( gameInfo.variant == VariantSChess )
8901            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8902
8903       if(overruled) {
8904         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8905                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8906            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8907            if(StrStr(cps->variants, b) == NULL) {
8908                // specific sized variant not known, check if general sizing allowed
8909                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8910                    if(StrStr(cps->variants, "boardsize") == NULL) {
8911                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8912                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8913                        DisplayFatalError(buf, 0, 1);
8914                        return;
8915                    }
8916                    /* [HGM] here we really should compare with the maximum supported board size */
8917                }
8918            }
8919       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8920       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8921       SendToProgram(buf, cps);
8922     }
8923     currentlyInitializedVariant = gameInfo.variant;
8924
8925     /* [HGM] send opening position in FRC to first engine */
8926     if(setup) {
8927           SendToProgram("force\n", cps);
8928           SendBoard(cps, 0);
8929           /* engine is now in force mode! Set flag to wake it up after first move. */
8930           setboardSpoiledMachineBlack = 1;
8931     }
8932
8933     if (cps->sendICS) {
8934       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8935       SendToProgram(buf, cps);
8936     }
8937     cps->maybeThinking = FALSE;
8938     cps->offeredDraw = 0;
8939     if (!appData.icsActive) {
8940         SendTimeControl(cps, movesPerSession, timeControl,
8941                         timeIncrement, appData.searchDepth,
8942                         searchTime);
8943     }
8944     if (appData.showThinking
8945         // [HGM] thinking: four options require thinking output to be sent
8946         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8947                                 ) {
8948         SendToProgram("post\n", cps);
8949     }
8950     SendToProgram("hard\n", cps);
8951     if (!appData.ponderNextMove) {
8952         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8953            it without being sure what state we are in first.  "hard"
8954            is not a toggle, so that one is OK.
8955          */
8956         SendToProgram("easy\n", cps);
8957     }
8958     if (cps->usePing) {
8959       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8960       SendToProgram(buf, cps);
8961     }
8962     cps->initDone = TRUE;
8963 }
8964
8965
8966 void
8967 StartChessProgram(cps)
8968      ChessProgramState *cps;
8969 {
8970     char buf[MSG_SIZ];
8971     int err;
8972
8973     if (appData.noChessProgram) return;
8974     cps->initDone = FALSE;
8975
8976     if (strcmp(cps->host, "localhost") == 0) {
8977         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8978     } else if (*appData.remoteShell == NULLCHAR) {
8979         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8980     } else {
8981         if (*appData.remoteUser == NULLCHAR) {
8982           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8983                     cps->program);
8984         } else {
8985           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8986                     cps->host, appData.remoteUser, cps->program);
8987         }
8988         err = StartChildProcess(buf, "", &cps->pr);
8989     }
8990
8991     if (err != 0) {
8992       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8993         DisplayFatalError(buf, err, 1);
8994         cps->pr = NoProc;
8995         cps->isr = NULL;
8996         return;
8997     }
8998
8999     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9000     if (cps->protocolVersion > 1) {
9001       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9002       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9003       cps->comboCnt = 0;  //                and values of combo boxes
9004       SendToProgram(buf, cps);
9005     } else {
9006       SendToProgram("xboard\n", cps);
9007     }
9008 }
9009
9010
9011 void
9012 TwoMachinesEventIfReady P((void))
9013 {
9014   if (first.lastPing != first.lastPong) {
9015     DisplayMessage("", _("Waiting for first chess program"));
9016     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9017     return;
9018   }
9019   if (second.lastPing != second.lastPong) {
9020     DisplayMessage("", _("Waiting for second chess program"));
9021     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9022     return;
9023   }
9024   ThawUI();
9025   TwoMachinesEvent();
9026 }
9027
9028 void
9029 NextMatchGame P((void))
9030 {
9031     int index; /* [HGM] autoinc: step load index during match */
9032     Reset(FALSE, TRUE);
9033     if (*appData.loadGameFile != NULLCHAR) {
9034         index = appData.loadGameIndex;
9035         if(index < 0) { // [HGM] autoinc
9036             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9037             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9038         }
9039         LoadGameFromFile(appData.loadGameFile,
9040                          index,
9041                          appData.loadGameFile, FALSE);
9042     } else if (*appData.loadPositionFile != NULLCHAR) {
9043         index = appData.loadPositionIndex;
9044         if(index < 0) { // [HGM] autoinc
9045             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9046             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9047         }
9048         LoadPositionFromFile(appData.loadPositionFile,
9049                              index,
9050                              appData.loadPositionFile);
9051     }
9052     TwoMachinesEventIfReady();
9053 }
9054
9055 void UserAdjudicationEvent( int result )
9056 {
9057     ChessMove gameResult = GameIsDrawn;
9058
9059     if( result > 0 ) {
9060         gameResult = WhiteWins;
9061     }
9062     else if( result < 0 ) {
9063         gameResult = BlackWins;
9064     }
9065
9066     if( gameMode == TwoMachinesPlay ) {
9067         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9068     }
9069 }
9070
9071
9072 // [HGM] save: calculate checksum of game to make games easily identifiable
9073 int StringCheckSum(char *s)
9074 {
9075         int i = 0;
9076         if(s==NULL) return 0;
9077         while(*s) i = i*259 + *s++;
9078         return i;
9079 }
9080
9081 int GameCheckSum()
9082 {
9083         int i, sum=0;
9084         for(i=backwardMostMove; i<forwardMostMove; i++) {
9085                 sum += pvInfoList[i].depth;
9086                 sum += StringCheckSum(parseList[i]);
9087                 sum += StringCheckSum(commentList[i]);
9088                 sum *= 261;
9089         }
9090         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9091         return sum + StringCheckSum(commentList[i]);
9092 } // end of save patch
9093
9094 void
9095 GameEnds(result, resultDetails, whosays)
9096      ChessMove result;
9097      char *resultDetails;
9098      int whosays;
9099 {
9100     GameMode nextGameMode;
9101     int isIcsGame;
9102     char buf[MSG_SIZ], popupRequested = 0;
9103
9104     if(endingGame) return; /* [HGM] crash: forbid recursion */
9105     endingGame = 1;
9106     if(twoBoards) { // [HGM] dual: switch back to one board
9107         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9108         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9109     }
9110     if (appData.debugMode) {
9111       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9112               result, resultDetails ? resultDetails : "(null)", whosays);
9113     }
9114
9115     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9116
9117     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9118         /* If we are playing on ICS, the server decides when the
9119            game is over, but the engine can offer to draw, claim
9120            a draw, or resign.
9121          */
9122 #if ZIPPY
9123         if (appData.zippyPlay && first.initDone) {
9124             if (result == GameIsDrawn) {
9125                 /* In case draw still needs to be claimed */
9126                 SendToICS(ics_prefix);
9127                 SendToICS("draw\n");
9128             } else if (StrCaseStr(resultDetails, "resign")) {
9129                 SendToICS(ics_prefix);
9130                 SendToICS("resign\n");
9131             }
9132         }
9133 #endif
9134         endingGame = 0; /* [HGM] crash */
9135         return;
9136     }
9137
9138     /* If we're loading the game from a file, stop */
9139     if (whosays == GE_FILE) {
9140       (void) StopLoadGameTimer();
9141       gameFileFP = NULL;
9142     }
9143
9144     /* Cancel draw offers */
9145     first.offeredDraw = second.offeredDraw = 0;
9146
9147     /* If this is an ICS game, only ICS can really say it's done;
9148        if not, anyone can. */
9149     isIcsGame = (gameMode == IcsPlayingWhite ||
9150                  gameMode == IcsPlayingBlack ||
9151                  gameMode == IcsObserving    ||
9152                  gameMode == IcsExamining);
9153
9154     if (!isIcsGame || whosays == GE_ICS) {
9155         /* OK -- not an ICS game, or ICS said it was done */
9156         StopClocks();
9157         if (!isIcsGame && !appData.noChessProgram)
9158           SetUserThinkingEnables();
9159
9160         /* [HGM] if a machine claims the game end we verify this claim */
9161         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9162             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9163                 char claimer;
9164                 ChessMove trueResult = (ChessMove) -1;
9165
9166                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9167                                             first.twoMachinesColor[0] :
9168                                             second.twoMachinesColor[0] ;
9169
9170                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9171                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9172                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9173                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9174                 } else
9175                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9176                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9177                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9178                 } else
9179                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9180                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9181                 }
9182
9183                 // now verify win claims, but not in drop games, as we don't understand those yet
9184                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9185                                                  || gameInfo.variant == VariantGreat) &&
9186                     (result == WhiteWins && claimer == 'w' ||
9187                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9188                       if (appData.debugMode) {
9189                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9190                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9191                       }
9192                       if(result != trueResult) {
9193                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9194                               result = claimer == 'w' ? BlackWins : WhiteWins;
9195                               resultDetails = buf;
9196                       }
9197                 } else
9198                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9199                     && (forwardMostMove <= backwardMostMove ||
9200                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9201                         (claimer=='b')==(forwardMostMove&1))
9202                                                                                   ) {
9203                       /* [HGM] verify: draws that were not flagged are false claims */
9204                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9205                       result = claimer == 'w' ? BlackWins : WhiteWins;
9206                       resultDetails = buf;
9207                 }
9208                 /* (Claiming a loss is accepted no questions asked!) */
9209             }
9210             /* [HGM] bare: don't allow bare King to win */
9211             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9212                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9213                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9214                && result != GameIsDrawn)
9215             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9216                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9217                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9218                         if(p >= 0 && p <= (int)WhiteKing) k++;
9219                 }
9220                 if (appData.debugMode) {
9221                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9222                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9223                 }
9224                 if(k <= 1) {
9225                         result = GameIsDrawn;
9226                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9227                         resultDetails = buf;
9228                 }
9229             }
9230         }
9231
9232
9233         if(serverMoves != NULL && !loadFlag) { char c = '=';
9234             if(result==WhiteWins) c = '+';
9235             if(result==BlackWins) c = '-';
9236             if(resultDetails != NULL)
9237                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9238         }
9239         if (resultDetails != NULL) {
9240             gameInfo.result = result;
9241             gameInfo.resultDetails = StrSave(resultDetails);
9242
9243             /* display last move only if game was not loaded from file */
9244             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9245                 DisplayMove(currentMove - 1);
9246
9247             if (forwardMostMove != 0) {
9248                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9249                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9250                                                                 ) {
9251                     if (*appData.saveGameFile != NULLCHAR) {
9252                         SaveGameToFile(appData.saveGameFile, TRUE);
9253                     } else if (appData.autoSaveGames) {
9254                         AutoSaveGame();
9255                     }
9256                     if (*appData.savePositionFile != NULLCHAR) {
9257                         SavePositionToFile(appData.savePositionFile);
9258                     }
9259                 }
9260             }
9261
9262             /* Tell program how game ended in case it is learning */
9263             /* [HGM] Moved this to after saving the PGN, just in case */
9264             /* engine died and we got here through time loss. In that */
9265             /* case we will get a fatal error writing the pipe, which */
9266             /* would otherwise lose us the PGN.                       */
9267             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9268             /* output during GameEnds should never be fatal anymore   */
9269             if (gameMode == MachinePlaysWhite ||
9270                 gameMode == MachinePlaysBlack ||
9271                 gameMode == TwoMachinesPlay ||
9272                 gameMode == IcsPlayingWhite ||
9273                 gameMode == IcsPlayingBlack ||
9274                 gameMode == BeginningOfGame) {
9275                 char buf[MSG_SIZ];
9276                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9277                         resultDetails);
9278                 if (first.pr != NoProc) {
9279                     SendToProgram(buf, &first);
9280                 }
9281                 if (second.pr != NoProc &&
9282                     gameMode == TwoMachinesPlay) {
9283                     SendToProgram(buf, &second);
9284                 }
9285             }
9286         }
9287
9288         if (appData.icsActive) {
9289             if (appData.quietPlay &&
9290                 (gameMode == IcsPlayingWhite ||
9291                  gameMode == IcsPlayingBlack)) {
9292                 SendToICS(ics_prefix);
9293                 SendToICS("set shout 1\n");
9294             }
9295             nextGameMode = IcsIdle;
9296             ics_user_moved = FALSE;
9297             /* clean up premove.  It's ugly when the game has ended and the
9298              * premove highlights are still on the board.
9299              */
9300             if (gotPremove) {
9301               gotPremove = FALSE;
9302               ClearPremoveHighlights();
9303               DrawPosition(FALSE, boards[currentMove]);
9304             }
9305             if (whosays == GE_ICS) {
9306                 switch (result) {
9307                 case WhiteWins:
9308                     if (gameMode == IcsPlayingWhite)
9309                         PlayIcsWinSound();
9310                     else if(gameMode == IcsPlayingBlack)
9311                         PlayIcsLossSound();
9312                     break;
9313                 case BlackWins:
9314                     if (gameMode == IcsPlayingBlack)
9315                         PlayIcsWinSound();
9316                     else if(gameMode == IcsPlayingWhite)
9317                         PlayIcsLossSound();
9318                     break;
9319                 case GameIsDrawn:
9320                     PlayIcsDrawSound();
9321                     break;
9322                 default:
9323                     PlayIcsUnfinishedSound();
9324                 }
9325             }
9326         } else if (gameMode == EditGame ||
9327                    gameMode == PlayFromGameFile ||
9328                    gameMode == AnalyzeMode ||
9329                    gameMode == AnalyzeFile) {
9330             nextGameMode = gameMode;
9331         } else {
9332             nextGameMode = EndOfGame;
9333         }
9334         pausing = FALSE;
9335         ModeHighlight();
9336     } else {
9337         nextGameMode = gameMode;
9338     }
9339
9340     if (appData.noChessProgram) {
9341         gameMode = nextGameMode;
9342         ModeHighlight();
9343         endingGame = 0; /* [HGM] crash */
9344         return;
9345     }
9346
9347     if (first.reuse) {
9348         /* Put first chess program into idle state */
9349         if (first.pr != NoProc &&
9350             (gameMode == MachinePlaysWhite ||
9351              gameMode == MachinePlaysBlack ||
9352              gameMode == TwoMachinesPlay ||
9353              gameMode == IcsPlayingWhite ||
9354              gameMode == IcsPlayingBlack ||
9355              gameMode == BeginningOfGame)) {
9356             SendToProgram("force\n", &first);
9357             if (first.usePing) {
9358               char buf[MSG_SIZ];
9359               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9360               SendToProgram(buf, &first);
9361             }
9362         }
9363     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9364         /* Kill off first chess program */
9365         if (first.isr != NULL)
9366           RemoveInputSource(first.isr);
9367         first.isr = NULL;
9368
9369         if (first.pr != NoProc) {
9370             ExitAnalyzeMode();
9371             DoSleep( appData.delayBeforeQuit );
9372             SendToProgram("quit\n", &first);
9373             DoSleep( appData.delayAfterQuit );
9374             DestroyChildProcess(first.pr, first.useSigterm);
9375         }
9376         first.pr = NoProc;
9377     }
9378     if (second.reuse) {
9379         /* Put second chess program into idle state */
9380         if (second.pr != NoProc &&
9381             gameMode == TwoMachinesPlay) {
9382             SendToProgram("force\n", &second);
9383             if (second.usePing) {
9384               char buf[MSG_SIZ];
9385               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9386               SendToProgram(buf, &second);
9387             }
9388         }
9389     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9390         /* Kill off second chess program */
9391         if (second.isr != NULL)
9392           RemoveInputSource(second.isr);
9393         second.isr = NULL;
9394
9395         if (second.pr != NoProc) {
9396             DoSleep( appData.delayBeforeQuit );
9397             SendToProgram("quit\n", &second);
9398             DoSleep( appData.delayAfterQuit );
9399             DestroyChildProcess(second.pr, second.useSigterm);
9400         }
9401         second.pr = NoProc;
9402     }
9403
9404     if (matchMode && gameMode == TwoMachinesPlay) {
9405         switch (result) {
9406         case WhiteWins:
9407           if (first.twoMachinesColor[0] == 'w') {
9408             first.matchWins++;
9409           } else {
9410             second.matchWins++;
9411           }
9412           break;
9413         case BlackWins:
9414           if (first.twoMachinesColor[0] == 'b') {
9415             first.matchWins++;
9416           } else {
9417             second.matchWins++;
9418           }
9419           break;
9420         default:
9421           break;
9422         }
9423         if (matchGame < appData.matchGames) {
9424             char *tmp;
9425             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9426                 tmp = first.twoMachinesColor;
9427                 first.twoMachinesColor = second.twoMachinesColor;
9428                 second.twoMachinesColor = tmp;
9429             }
9430             gameMode = nextGameMode;
9431             matchGame++;
9432             if(appData.matchPause>10000 || appData.matchPause<10)
9433                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9434             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9435             endingGame = 0; /* [HGM] crash */
9436             return;
9437         } else {
9438             gameMode = nextGameMode;
9439             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9440                      first.tidy, second.tidy,
9441                      first.matchWins, second.matchWins,
9442                      appData.matchGames - (first.matchWins + second.matchWins));
9443             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9444         }
9445     }
9446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9447         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9448       ExitAnalyzeMode();
9449     gameMode = nextGameMode;
9450     ModeHighlight();
9451     endingGame = 0;  /* [HGM] crash */
9452     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9453       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9454         matchMode = FALSE; appData.matchGames = matchGame = 0;
9455         DisplayNote(buf);
9456       }
9457     }
9458 }
9459
9460 /* Assumes program was just initialized (initString sent).
9461    Leaves program in force mode. */
9462 void
9463 FeedMovesToProgram(cps, upto)
9464      ChessProgramState *cps;
9465      int upto;
9466 {
9467     int i;
9468
9469     if (appData.debugMode)
9470       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9471               startedFromSetupPosition ? "position and " : "",
9472               backwardMostMove, upto, cps->which);
9473     if(currentlyInitializedVariant != gameInfo.variant) {
9474       char buf[MSG_SIZ];
9475         // [HGM] variantswitch: make engine aware of new variant
9476         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9477                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9478         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9479         SendToProgram(buf, cps);
9480         currentlyInitializedVariant = gameInfo.variant;
9481     }
9482     SendToProgram("force\n", cps);
9483     if (startedFromSetupPosition) {
9484         SendBoard(cps, backwardMostMove);
9485     if (appData.debugMode) {
9486         fprintf(debugFP, "feedMoves\n");
9487     }
9488     }
9489     for (i = backwardMostMove; i < upto; i++) {
9490         SendMoveToProgram(i, cps);
9491     }
9492 }
9493
9494
9495 void
9496 ResurrectChessProgram()
9497 {
9498      /* The chess program may have exited.
9499         If so, restart it and feed it all the moves made so far. */
9500
9501     if (appData.noChessProgram || first.pr != NoProc) return;
9502
9503     StartChessProgram(&first);
9504     InitChessProgram(&first, FALSE);
9505     FeedMovesToProgram(&first, currentMove);
9506
9507     if (!first.sendTime) {
9508         /* can't tell gnuchess what its clock should read,
9509            so we bow to its notion. */
9510         ResetClocks();
9511         timeRemaining[0][currentMove] = whiteTimeRemaining;
9512         timeRemaining[1][currentMove] = blackTimeRemaining;
9513     }
9514
9515     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9516                 appData.icsEngineAnalyze) && first.analysisSupport) {
9517       SendToProgram("analyze\n", &first);
9518       first.analyzing = TRUE;
9519     }
9520 }
9521
9522 /*
9523  * Button procedures
9524  */
9525 void
9526 Reset(redraw, init)
9527      int redraw, init;
9528 {
9529     int i;
9530
9531     if (appData.debugMode) {
9532         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9533                 redraw, init, gameMode);
9534     }
9535     CleanupTail(); // [HGM] vari: delete any stored variations
9536     pausing = pauseExamInvalid = FALSE;
9537     startedFromSetupPosition = blackPlaysFirst = FALSE;
9538     firstMove = TRUE;
9539     whiteFlag = blackFlag = FALSE;
9540     userOfferedDraw = FALSE;
9541     hintRequested = bookRequested = FALSE;
9542     first.maybeThinking = FALSE;
9543     second.maybeThinking = FALSE;
9544     first.bookSuspend = FALSE; // [HGM] book
9545     second.bookSuspend = FALSE;
9546     thinkOutput[0] = NULLCHAR;
9547     lastHint[0] = NULLCHAR;
9548     ClearGameInfo(&gameInfo);
9549     gameInfo.variant = StringToVariant(appData.variant);
9550     ics_user_moved = ics_clock_paused = FALSE;
9551     ics_getting_history = H_FALSE;
9552     ics_gamenum = -1;
9553     white_holding[0] = black_holding[0] = NULLCHAR;
9554     ClearProgramStats();
9555     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9556
9557     ResetFrontEnd();
9558     ClearHighlights();
9559     flipView = appData.flipView;
9560     ClearPremoveHighlights();
9561     gotPremove = FALSE;
9562     alarmSounded = FALSE;
9563
9564     GameEnds(EndOfFile, NULL, GE_PLAYER);
9565     if(appData.serverMovesName != NULL) {
9566         /* [HGM] prepare to make moves file for broadcasting */
9567         clock_t t = clock();
9568         if(serverMoves != NULL) fclose(serverMoves);
9569         serverMoves = fopen(appData.serverMovesName, "r");
9570         if(serverMoves != NULL) {
9571             fclose(serverMoves);
9572             /* delay 15 sec before overwriting, so all clients can see end */
9573             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9574         }
9575         serverMoves = fopen(appData.serverMovesName, "w");
9576     }
9577
9578     ExitAnalyzeMode();
9579     gameMode = BeginningOfGame;
9580     ModeHighlight();
9581     if(appData.icsActive) gameInfo.variant = VariantNormal;
9582     currentMove = forwardMostMove = backwardMostMove = 0;
9583     InitPosition(redraw);
9584     for (i = 0; i < MAX_MOVES; i++) {
9585         if (commentList[i] != NULL) {
9586             free(commentList[i]);
9587             commentList[i] = NULL;
9588         }
9589     }
9590     ResetClocks();
9591     timeRemaining[0][0] = whiteTimeRemaining;
9592     timeRemaining[1][0] = blackTimeRemaining;
9593     if (first.pr == NULL) {
9594         StartChessProgram(&first);
9595     }
9596     if (init) {
9597             InitChessProgram(&first, startedFromSetupPosition);
9598     }
9599     DisplayTitle("");
9600     DisplayMessage("", "");
9601     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9602     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9603 }
9604
9605 void
9606 AutoPlayGameLoop()
9607 {
9608     for (;;) {
9609         if (!AutoPlayOneMove())
9610           return;
9611         if (matchMode || appData.timeDelay == 0)
9612           continue;
9613         if (appData.timeDelay < 0)
9614           return;
9615         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9616         break;
9617     }
9618 }
9619
9620
9621 int
9622 AutoPlayOneMove()
9623 {
9624     int fromX, fromY, toX, toY;
9625
9626     if (appData.debugMode) {
9627       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9628     }
9629
9630     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9631       return FALSE;
9632
9633     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9634       pvInfoList[currentMove].depth = programStats.depth;
9635       pvInfoList[currentMove].score = programStats.score;
9636       pvInfoList[currentMove].time  = 0;
9637       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9638     }
9639
9640     if (currentMove >= forwardMostMove) {
9641       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9642       gameMode = EditGame;
9643       ModeHighlight();
9644
9645       /* [AS] Clear current move marker at the end of a game */
9646       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9647
9648       return FALSE;
9649     }
9650
9651     toX = moveList[currentMove][2] - AAA;
9652     toY = moveList[currentMove][3] - ONE;
9653
9654     if (moveList[currentMove][1] == '@') {
9655         if (appData.highlightLastMove) {
9656             SetHighlights(-1, -1, toX, toY);
9657         }
9658     } else {
9659         fromX = moveList[currentMove][0] - AAA;
9660         fromY = moveList[currentMove][1] - ONE;
9661
9662         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9663
9664         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9665
9666         if (appData.highlightLastMove) {
9667             SetHighlights(fromX, fromY, toX, toY);
9668         }
9669     }
9670     DisplayMove(currentMove);
9671     SendMoveToProgram(currentMove++, &first);
9672     DisplayBothClocks();
9673     DrawPosition(FALSE, boards[currentMove]);
9674     // [HGM] PV info: always display, routine tests if empty
9675     DisplayComment(currentMove - 1, commentList[currentMove]);
9676     return TRUE;
9677 }
9678
9679
9680 int
9681 LoadGameOneMove(readAhead)
9682      ChessMove readAhead;
9683 {
9684     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9685     char promoChar = NULLCHAR;
9686     ChessMove moveType;
9687     char move[MSG_SIZ];
9688     char *p, *q;
9689
9690     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9691         gameMode != AnalyzeMode && gameMode != Training) {
9692         gameFileFP = NULL;
9693         return FALSE;
9694     }
9695
9696     yyboardindex = forwardMostMove;
9697     if (readAhead != EndOfFile) {
9698       moveType = readAhead;
9699     } else {
9700       if (gameFileFP == NULL)
9701           return FALSE;
9702       moveType = (ChessMove) Myylex();
9703     }
9704
9705     done = FALSE;
9706     switch (moveType) {
9707       case Comment:
9708         if (appData.debugMode)
9709           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9710         p = yy_text;
9711
9712         /* append the comment but don't display it */
9713         AppendComment(currentMove, p, FALSE);
9714         return TRUE;
9715
9716       case WhiteCapturesEnPassant:
9717       case BlackCapturesEnPassant:
9718       case WhitePromotion:
9719       case BlackPromotion:
9720       case WhiteNonPromotion:
9721       case BlackNonPromotion:
9722       case NormalMove:
9723       case WhiteKingSideCastle:
9724       case WhiteQueenSideCastle:
9725       case BlackKingSideCastle:
9726       case BlackQueenSideCastle:
9727       case WhiteKingSideCastleWild:
9728       case WhiteQueenSideCastleWild:
9729       case BlackKingSideCastleWild:
9730       case BlackQueenSideCastleWild:
9731       /* PUSH Fabien */
9732       case WhiteHSideCastleFR:
9733       case WhiteASideCastleFR:
9734       case BlackHSideCastleFR:
9735       case BlackASideCastleFR:
9736       /* POP Fabien */
9737         if (appData.debugMode)
9738           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9739         fromX = currentMoveString[0] - AAA;
9740         fromY = currentMoveString[1] - ONE;
9741         toX = currentMoveString[2] - AAA;
9742         toY = currentMoveString[3] - ONE;
9743         promoChar = currentMoveString[4];
9744         break;
9745
9746       case WhiteDrop:
9747       case BlackDrop:
9748         if (appData.debugMode)
9749           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9750         fromX = moveType == WhiteDrop ?
9751           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9752         (int) CharToPiece(ToLower(currentMoveString[0]));
9753         fromY = DROP_RANK;
9754         toX = currentMoveString[2] - AAA;
9755         toY = currentMoveString[3] - ONE;
9756         break;
9757
9758       case WhiteWins:
9759       case BlackWins:
9760       case GameIsDrawn:
9761       case GameUnfinished:
9762         if (appData.debugMode)
9763           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9764         p = strchr(yy_text, '{');
9765         if (p == NULL) p = strchr(yy_text, '(');
9766         if (p == NULL) {
9767             p = yy_text;
9768             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9769         } else {
9770             q = strchr(p, *p == '{' ? '}' : ')');
9771             if (q != NULL) *q = NULLCHAR;
9772             p++;
9773         }
9774         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9775         GameEnds(moveType, p, GE_FILE);
9776         done = TRUE;
9777         if (cmailMsgLoaded) {
9778             ClearHighlights();
9779             flipView = WhiteOnMove(currentMove);
9780             if (moveType == GameUnfinished) flipView = !flipView;
9781             if (appData.debugMode)
9782               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9783         }
9784         break;
9785
9786       case EndOfFile:
9787         if (appData.debugMode)
9788           fprintf(debugFP, "Parser hit end of file\n");
9789         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9790           case MT_NONE:
9791           case MT_CHECK:
9792             break;
9793           case MT_CHECKMATE:
9794           case MT_STAINMATE:
9795             if (WhiteOnMove(currentMove)) {
9796                 GameEnds(BlackWins, "Black mates", GE_FILE);
9797             } else {
9798                 GameEnds(WhiteWins, "White mates", GE_FILE);
9799             }
9800             break;
9801           case MT_STALEMATE:
9802             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9803             break;
9804         }
9805         done = TRUE;
9806         break;
9807
9808       case MoveNumberOne:
9809         if (lastLoadGameStart == GNUChessGame) {
9810             /* GNUChessGames have numbers, but they aren't move numbers */
9811             if (appData.debugMode)
9812               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9813                       yy_text, (int) moveType);
9814             return LoadGameOneMove(EndOfFile); /* tail recursion */
9815         }
9816         /* else fall thru */
9817
9818       case XBoardGame:
9819       case GNUChessGame:
9820       case PGNTag:
9821         /* Reached start of next game in file */
9822         if (appData.debugMode)
9823           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9824         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9825           case MT_NONE:
9826           case MT_CHECK:
9827             break;
9828           case MT_CHECKMATE:
9829           case MT_STAINMATE:
9830             if (WhiteOnMove(currentMove)) {
9831                 GameEnds(BlackWins, "Black mates", GE_FILE);
9832             } else {
9833                 GameEnds(WhiteWins, "White mates", GE_FILE);
9834             }
9835             break;
9836           case MT_STALEMATE:
9837             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9838             break;
9839         }
9840         done = TRUE;
9841         break;
9842
9843       case PositionDiagram:     /* should not happen; ignore */
9844       case ElapsedTime:         /* ignore */
9845       case NAG:                 /* ignore */
9846         if (appData.debugMode)
9847           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9848                   yy_text, (int) moveType);
9849         return LoadGameOneMove(EndOfFile); /* tail recursion */
9850
9851       case IllegalMove:
9852         if (appData.testLegality) {
9853             if (appData.debugMode)
9854               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9855             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9856                     (forwardMostMove / 2) + 1,
9857                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9858             DisplayError(move, 0);
9859             done = TRUE;
9860         } else {
9861             if (appData.debugMode)
9862               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9863                       yy_text, currentMoveString);
9864             fromX = currentMoveString[0] - AAA;
9865             fromY = currentMoveString[1] - ONE;
9866             toX = currentMoveString[2] - AAA;
9867             toY = currentMoveString[3] - ONE;
9868             promoChar = currentMoveString[4];
9869         }
9870         break;
9871
9872       case AmbiguousMove:
9873         if (appData.debugMode)
9874           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9875         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9876                 (forwardMostMove / 2) + 1,
9877                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9878         DisplayError(move, 0);
9879         done = TRUE;
9880         break;
9881
9882       default:
9883       case ImpossibleMove:
9884         if (appData.debugMode)
9885           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9886         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9887                 (forwardMostMove / 2) + 1,
9888                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9889         DisplayError(move, 0);
9890         done = TRUE;
9891         break;
9892     }
9893
9894     if (done) {
9895         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9896             DrawPosition(FALSE, boards[currentMove]);
9897             DisplayBothClocks();
9898             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9899               DisplayComment(currentMove - 1, commentList[currentMove]);
9900         }
9901         (void) StopLoadGameTimer();
9902         gameFileFP = NULL;
9903         cmailOldMove = forwardMostMove;
9904         return FALSE;
9905     } else {
9906         /* currentMoveString is set as a side-effect of yylex */
9907         strcat(currentMoveString, "\n");
9908         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9909
9910         thinkOutput[0] = NULLCHAR;
9911         MakeMove(fromX, fromY, toX, toY, promoChar);
9912         currentMove = forwardMostMove;
9913         return TRUE;
9914     }
9915 }
9916
9917 /* Load the nth game from the given file */
9918 int
9919 LoadGameFromFile(filename, n, title, useList)
9920      char *filename;
9921      int n;
9922      char *title;
9923      /*Boolean*/ int useList;
9924 {
9925     FILE *f;
9926     char buf[MSG_SIZ];
9927
9928     if (strcmp(filename, "-") == 0) {
9929         f = stdin;
9930         title = "stdin";
9931     } else {
9932         f = fopen(filename, "rb");
9933         if (f == NULL) {
9934           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9935             DisplayError(buf, errno);
9936             return FALSE;
9937         }
9938     }
9939     if (fseek(f, 0, 0) == -1) {
9940         /* f is not seekable; probably a pipe */
9941         useList = FALSE;
9942     }
9943     if (useList && n == 0) {
9944         int error = GameListBuild(f);
9945         if (error) {
9946             DisplayError(_("Cannot build game list"), error);
9947         } else if (!ListEmpty(&gameList) &&
9948                    ((ListGame *) gameList.tailPred)->number > 1) {
9949             GameListPopUp(f, title);
9950             return TRUE;
9951         }
9952         GameListDestroy();
9953         n = 1;
9954     }
9955     if (n == 0) n = 1;
9956     return LoadGame(f, n, title, FALSE);
9957 }
9958
9959
9960 void
9961 MakeRegisteredMove()
9962 {
9963     int fromX, fromY, toX, toY;
9964     char promoChar;
9965     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9966         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9967           case CMAIL_MOVE:
9968           case CMAIL_DRAW:
9969             if (appData.debugMode)
9970               fprintf(debugFP, "Restoring %s for game %d\n",
9971                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9972
9973             thinkOutput[0] = NULLCHAR;
9974             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9975             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9976             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9977             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9978             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9979             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9980             MakeMove(fromX, fromY, toX, toY, promoChar);
9981             ShowMove(fromX, fromY, toX, toY);
9982
9983             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9984               case MT_NONE:
9985               case MT_CHECK:
9986                 break;
9987
9988               case MT_CHECKMATE:
9989               case MT_STAINMATE:
9990                 if (WhiteOnMove(currentMove)) {
9991                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9992                 } else {
9993                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9994                 }
9995                 break;
9996
9997               case MT_STALEMATE:
9998                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9999                 break;
10000             }
10001
10002             break;
10003
10004           case CMAIL_RESIGN:
10005             if (WhiteOnMove(currentMove)) {
10006                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10007             } else {
10008                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10009             }
10010             break;
10011
10012           case CMAIL_ACCEPT:
10013             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10014             break;
10015
10016           default:
10017             break;
10018         }
10019     }
10020
10021     return;
10022 }
10023
10024 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10025 int
10026 CmailLoadGame(f, gameNumber, title, useList)
10027      FILE *f;
10028      int gameNumber;
10029      char *title;
10030      int useList;
10031 {
10032     int retVal;
10033
10034     if (gameNumber > nCmailGames) {
10035         DisplayError(_("No more games in this message"), 0);
10036         return FALSE;
10037     }
10038     if (f == lastLoadGameFP) {
10039         int offset = gameNumber - lastLoadGameNumber;
10040         if (offset == 0) {
10041             cmailMsg[0] = NULLCHAR;
10042             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10043                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10044                 nCmailMovesRegistered--;
10045             }
10046             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10047             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10048                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10049             }
10050         } else {
10051             if (! RegisterMove()) return FALSE;
10052         }
10053     }
10054
10055     retVal = LoadGame(f, gameNumber, title, useList);
10056
10057     /* Make move registered during previous look at this game, if any */
10058     MakeRegisteredMove();
10059
10060     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10061         commentList[currentMove]
10062           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10063         DisplayComment(currentMove - 1, commentList[currentMove]);
10064     }
10065
10066     return retVal;
10067 }
10068
10069 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10070 int
10071 ReloadGame(offset)
10072      int offset;
10073 {
10074     int gameNumber = lastLoadGameNumber + offset;
10075     if (lastLoadGameFP == NULL) {
10076         DisplayError(_("No game has been loaded yet"), 0);
10077         return FALSE;
10078     }
10079     if (gameNumber <= 0) {
10080         DisplayError(_("Can't back up any further"), 0);
10081         return FALSE;
10082     }
10083     if (cmailMsgLoaded) {
10084         return CmailLoadGame(lastLoadGameFP, gameNumber,
10085                              lastLoadGameTitle, lastLoadGameUseList);
10086     } else {
10087         return LoadGame(lastLoadGameFP, gameNumber,
10088                         lastLoadGameTitle, lastLoadGameUseList);
10089     }
10090 }
10091
10092
10093
10094 /* Load the nth game from open file f */
10095 int
10096 LoadGame(f, gameNumber, title, useList)
10097      FILE *f;
10098      int gameNumber;
10099      char *title;
10100      int useList;
10101 {
10102     ChessMove cm;
10103     char buf[MSG_SIZ];
10104     int gn = gameNumber;
10105     ListGame *lg = NULL;
10106     int numPGNTags = 0;
10107     int err;
10108     GameMode oldGameMode;
10109     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10110
10111     if (appData.debugMode)
10112         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10113
10114     if (gameMode == Training )
10115         SetTrainingModeOff();
10116
10117     oldGameMode = gameMode;
10118     if (gameMode != BeginningOfGame) {
10119       Reset(FALSE, TRUE);
10120     }
10121
10122     gameFileFP = f;
10123     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10124         fclose(lastLoadGameFP);
10125     }
10126
10127     if (useList) {
10128         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10129
10130         if (lg) {
10131             fseek(f, lg->offset, 0);
10132             GameListHighlight(gameNumber);
10133             gn = 1;
10134         }
10135         else {
10136             DisplayError(_("Game number out of range"), 0);
10137             return FALSE;
10138         }
10139     } else {
10140         GameListDestroy();
10141         if (fseek(f, 0, 0) == -1) {
10142             if (f == lastLoadGameFP ?
10143                 gameNumber == lastLoadGameNumber + 1 :
10144                 gameNumber == 1) {
10145                 gn = 1;
10146             } else {
10147                 DisplayError(_("Can't seek on game file"), 0);
10148                 return FALSE;
10149             }
10150         }
10151     }
10152     lastLoadGameFP = f;
10153     lastLoadGameNumber = gameNumber;
10154     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10155     lastLoadGameUseList = useList;
10156
10157     yynewfile(f);
10158
10159     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10160       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10161                 lg->gameInfo.black);
10162             DisplayTitle(buf);
10163     } else if (*title != NULLCHAR) {
10164         if (gameNumber > 1) {
10165           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10166             DisplayTitle(buf);
10167         } else {
10168             DisplayTitle(title);
10169         }
10170     }
10171
10172     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10173         gameMode = PlayFromGameFile;
10174         ModeHighlight();
10175     }
10176
10177     currentMove = forwardMostMove = backwardMostMove = 0;
10178     CopyBoard(boards[0], initialPosition);
10179     StopClocks();
10180
10181     /*
10182      * Skip the first gn-1 games in the file.
10183      * Also skip over anything that precedes an identifiable
10184      * start of game marker, to avoid being confused by
10185      * garbage at the start of the file.  Currently
10186      * recognized start of game markers are the move number "1",
10187      * the pattern "gnuchess .* game", the pattern
10188      * "^[#;%] [^ ]* game file", and a PGN tag block.
10189      * A game that starts with one of the latter two patterns
10190      * will also have a move number 1, possibly
10191      * following a position diagram.
10192      * 5-4-02: Let's try being more lenient and allowing a game to
10193      * start with an unnumbered move.  Does that break anything?
10194      */
10195     cm = lastLoadGameStart = EndOfFile;
10196     while (gn > 0) {
10197         yyboardindex = forwardMostMove;
10198         cm = (ChessMove) Myylex();
10199         switch (cm) {
10200           case EndOfFile:
10201             if (cmailMsgLoaded) {
10202                 nCmailGames = CMAIL_MAX_GAMES - gn;
10203             } else {
10204                 Reset(TRUE, TRUE);
10205                 DisplayError(_("Game not found in file"), 0);
10206             }
10207             return FALSE;
10208
10209           case GNUChessGame:
10210           case XBoardGame:
10211             gn--;
10212             lastLoadGameStart = cm;
10213             break;
10214
10215           case MoveNumberOne:
10216             switch (lastLoadGameStart) {
10217               case GNUChessGame:
10218               case XBoardGame:
10219               case PGNTag:
10220                 break;
10221               case MoveNumberOne:
10222               case EndOfFile:
10223                 gn--;           /* count this game */
10224                 lastLoadGameStart = cm;
10225                 break;
10226               default:
10227                 /* impossible */
10228                 break;
10229             }
10230             break;
10231
10232           case PGNTag:
10233             switch (lastLoadGameStart) {
10234               case GNUChessGame:
10235               case PGNTag:
10236               case MoveNumberOne:
10237               case EndOfFile:
10238                 gn--;           /* count this game */
10239                 lastLoadGameStart = cm;
10240                 break;
10241               case XBoardGame:
10242                 lastLoadGameStart = cm; /* game counted already */
10243                 break;
10244               default:
10245                 /* impossible */
10246                 break;
10247             }
10248             if (gn > 0) {
10249                 do {
10250                     yyboardindex = forwardMostMove;
10251                     cm = (ChessMove) Myylex();
10252                 } while (cm == PGNTag || cm == Comment);
10253             }
10254             break;
10255
10256           case WhiteWins:
10257           case BlackWins:
10258           case GameIsDrawn:
10259             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10260                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10261                     != CMAIL_OLD_RESULT) {
10262                     nCmailResults ++ ;
10263                     cmailResult[  CMAIL_MAX_GAMES
10264                                 - gn - 1] = CMAIL_OLD_RESULT;
10265                 }
10266             }
10267             break;
10268
10269           case NormalMove:
10270             /* Only a NormalMove can be at the start of a game
10271              * without a position diagram. */
10272             if (lastLoadGameStart == EndOfFile ) {
10273               gn--;
10274               lastLoadGameStart = MoveNumberOne;
10275             }
10276             break;
10277
10278           default:
10279             break;
10280         }
10281     }
10282
10283     if (appData.debugMode)
10284       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10285
10286     if (cm == XBoardGame) {
10287         /* Skip any header junk before position diagram and/or move 1 */
10288         for (;;) {
10289             yyboardindex = forwardMostMove;
10290             cm = (ChessMove) Myylex();
10291
10292             if (cm == EndOfFile ||
10293                 cm == GNUChessGame || cm == XBoardGame) {
10294                 /* Empty game; pretend end-of-file and handle later */
10295                 cm = EndOfFile;
10296                 break;
10297             }
10298
10299             if (cm == MoveNumberOne || cm == PositionDiagram ||
10300                 cm == PGNTag || cm == Comment)
10301               break;
10302         }
10303     } else if (cm == GNUChessGame) {
10304         if (gameInfo.event != NULL) {
10305             free(gameInfo.event);
10306         }
10307         gameInfo.event = StrSave(yy_text);
10308     }
10309
10310     startedFromSetupPosition = FALSE;
10311     while (cm == PGNTag) {
10312         if (appData.debugMode)
10313           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10314         err = ParsePGNTag(yy_text, &gameInfo);
10315         if (!err) numPGNTags++;
10316
10317         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10318         if(gameInfo.variant != oldVariant) {
10319             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10320             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10321             InitPosition(TRUE);
10322             oldVariant = gameInfo.variant;
10323             if (appData.debugMode)
10324               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10325         }
10326
10327
10328         if (gameInfo.fen != NULL) {
10329           Board initial_position;
10330           startedFromSetupPosition = TRUE;
10331           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10332             Reset(TRUE, TRUE);
10333             DisplayError(_("Bad FEN position in file"), 0);
10334             return FALSE;
10335           }
10336           CopyBoard(boards[0], initial_position);
10337           if (blackPlaysFirst) {
10338             currentMove = forwardMostMove = backwardMostMove = 1;
10339             CopyBoard(boards[1], initial_position);
10340             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10341             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10342             timeRemaining[0][1] = whiteTimeRemaining;
10343             timeRemaining[1][1] = blackTimeRemaining;
10344             if (commentList[0] != NULL) {
10345               commentList[1] = commentList[0];
10346               commentList[0] = NULL;
10347             }
10348           } else {
10349             currentMove = forwardMostMove = backwardMostMove = 0;
10350           }
10351           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10352           {   int i;
10353               initialRulePlies = FENrulePlies;
10354               for( i=0; i< nrCastlingRights; i++ )
10355                   initialRights[i] = initial_position[CASTLING][i];
10356           }
10357           yyboardindex = forwardMostMove;
10358           free(gameInfo.fen);
10359           gameInfo.fen = NULL;
10360         }
10361
10362         yyboardindex = forwardMostMove;
10363         cm = (ChessMove) Myylex();
10364
10365         /* Handle comments interspersed among the tags */
10366         while (cm == Comment) {
10367             char *p;
10368             if (appData.debugMode)
10369               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10370             p = yy_text;
10371             AppendComment(currentMove, p, FALSE);
10372             yyboardindex = forwardMostMove;
10373             cm = (ChessMove) Myylex();
10374         }
10375     }
10376
10377     /* don't rely on existence of Event tag since if game was
10378      * pasted from clipboard the Event tag may not exist
10379      */
10380     if (numPGNTags > 0){
10381         char *tags;
10382         if (gameInfo.variant == VariantNormal) {
10383           VariantClass v = StringToVariant(gameInfo.event);
10384           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10385           if(v < VariantShogi) gameInfo.variant = v;
10386         }
10387         if (!matchMode) {
10388           if( appData.autoDisplayTags ) {
10389             tags = PGNTags(&gameInfo);
10390             TagsPopUp(tags, CmailMsg());
10391             free(tags);
10392           }
10393         }
10394     } else {
10395         /* Make something up, but don't display it now */
10396         SetGameInfo();
10397         TagsPopDown();
10398     }
10399
10400     if (cm == PositionDiagram) {
10401         int i, j;
10402         char *p;
10403         Board initial_position;
10404
10405         if (appData.debugMode)
10406           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10407
10408         if (!startedFromSetupPosition) {
10409             p = yy_text;
10410             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10411               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10412                 switch (*p) {
10413                   case '[':
10414                   case '-':
10415                   case ' ':
10416                   case '\t':
10417                   case '\n':
10418                   case '\r':
10419                     break;
10420                   default:
10421                     initial_position[i][j++] = CharToPiece(*p);
10422                     break;
10423                 }
10424             while (*p == ' ' || *p == '\t' ||
10425                    *p == '\n' || *p == '\r') p++;
10426
10427             if (strncmp(p, "black", strlen("black"))==0)
10428               blackPlaysFirst = TRUE;
10429             else
10430               blackPlaysFirst = FALSE;
10431             startedFromSetupPosition = TRUE;
10432
10433             CopyBoard(boards[0], initial_position);
10434             if (blackPlaysFirst) {
10435                 currentMove = forwardMostMove = backwardMostMove = 1;
10436                 CopyBoard(boards[1], initial_position);
10437                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10438                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10439                 timeRemaining[0][1] = whiteTimeRemaining;
10440                 timeRemaining[1][1] = blackTimeRemaining;
10441                 if (commentList[0] != NULL) {
10442                     commentList[1] = commentList[0];
10443                     commentList[0] = NULL;
10444                 }
10445             } else {
10446                 currentMove = forwardMostMove = backwardMostMove = 0;
10447             }
10448         }
10449         yyboardindex = forwardMostMove;
10450         cm = (ChessMove) Myylex();
10451     }
10452
10453     if (first.pr == NoProc) {
10454         StartChessProgram(&first);
10455     }
10456     InitChessProgram(&first, FALSE);
10457     SendToProgram("force\n", &first);
10458     if (startedFromSetupPosition) {
10459         SendBoard(&first, forwardMostMove);
10460     if (appData.debugMode) {
10461         fprintf(debugFP, "Load Game\n");
10462     }
10463         DisplayBothClocks();
10464     }
10465
10466     /* [HGM] server: flag to write setup moves in broadcast file as one */
10467     loadFlag = appData.suppressLoadMoves;
10468
10469     while (cm == Comment) {
10470         char *p;
10471         if (appData.debugMode)
10472           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10473         p = yy_text;
10474         AppendComment(currentMove, p, FALSE);
10475         yyboardindex = forwardMostMove;
10476         cm = (ChessMove) Myylex();
10477     }
10478
10479     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10480         cm == WhiteWins || cm == BlackWins ||
10481         cm == GameIsDrawn || cm == GameUnfinished) {
10482         DisplayMessage("", _("No moves in game"));
10483         if (cmailMsgLoaded) {
10484             if (appData.debugMode)
10485               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10486             ClearHighlights();
10487             flipView = FALSE;
10488         }
10489         DrawPosition(FALSE, boards[currentMove]);
10490         DisplayBothClocks();
10491         gameMode = EditGame;
10492         ModeHighlight();
10493         gameFileFP = NULL;
10494         cmailOldMove = 0;
10495         return TRUE;
10496     }
10497
10498     // [HGM] PV info: routine tests if comment empty
10499     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10500         DisplayComment(currentMove - 1, commentList[currentMove]);
10501     }
10502     if (!matchMode && appData.timeDelay != 0)
10503       DrawPosition(FALSE, boards[currentMove]);
10504
10505     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10506       programStats.ok_to_send = 1;
10507     }
10508
10509     /* if the first token after the PGN tags is a move
10510      * and not move number 1, retrieve it from the parser
10511      */
10512     if (cm != MoveNumberOne)
10513         LoadGameOneMove(cm);
10514
10515     /* load the remaining moves from the file */
10516     while (LoadGameOneMove(EndOfFile)) {
10517       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10518       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10519     }
10520
10521     /* rewind to the start of the game */
10522     currentMove = backwardMostMove;
10523
10524     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10525
10526     if (oldGameMode == AnalyzeFile ||
10527         oldGameMode == AnalyzeMode) {
10528       AnalyzeFileEvent();
10529     }
10530
10531     if (matchMode || appData.timeDelay == 0) {
10532       ToEndEvent();
10533       gameMode = EditGame;
10534       ModeHighlight();
10535     } else if (appData.timeDelay > 0) {
10536       AutoPlayGameLoop();
10537     }
10538
10539     if (appData.debugMode)
10540         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10541
10542     loadFlag = 0; /* [HGM] true game starts */
10543     return TRUE;
10544 }
10545
10546 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10547 int
10548 ReloadPosition(offset)
10549      int offset;
10550 {
10551     int positionNumber = lastLoadPositionNumber + offset;
10552     if (lastLoadPositionFP == NULL) {
10553         DisplayError(_("No position has been loaded yet"), 0);
10554         return FALSE;
10555     }
10556     if (positionNumber <= 0) {
10557         DisplayError(_("Can't back up any further"), 0);
10558         return FALSE;
10559     }
10560     return LoadPosition(lastLoadPositionFP, positionNumber,
10561                         lastLoadPositionTitle);
10562 }
10563
10564 /* Load the nth position from the given file */
10565 int
10566 LoadPositionFromFile(filename, n, title)
10567      char *filename;
10568      int n;
10569      char *title;
10570 {
10571     FILE *f;
10572     char buf[MSG_SIZ];
10573
10574     if (strcmp(filename, "-") == 0) {
10575         return LoadPosition(stdin, n, "stdin");
10576     } else {
10577         f = fopen(filename, "rb");
10578         if (f == NULL) {
10579             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10580             DisplayError(buf, errno);
10581             return FALSE;
10582         } else {
10583             return LoadPosition(f, n, title);
10584         }
10585     }
10586 }
10587
10588 /* Load the nth position from the given open file, and close it */
10589 int
10590 LoadPosition(f, positionNumber, title)
10591      FILE *f;
10592      int positionNumber;
10593      char *title;
10594 {
10595     char *p, line[MSG_SIZ];
10596     Board initial_position;
10597     int i, j, fenMode, pn;
10598
10599     if (gameMode == Training )
10600         SetTrainingModeOff();
10601
10602     if (gameMode != BeginningOfGame) {
10603         Reset(FALSE, TRUE);
10604     }
10605     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10606         fclose(lastLoadPositionFP);
10607     }
10608     if (positionNumber == 0) positionNumber = 1;
10609     lastLoadPositionFP = f;
10610     lastLoadPositionNumber = positionNumber;
10611     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10612     if (first.pr == NoProc) {
10613       StartChessProgram(&first);
10614       InitChessProgram(&first, FALSE);
10615     }
10616     pn = positionNumber;
10617     if (positionNumber < 0) {
10618         /* Negative position number means to seek to that byte offset */
10619         if (fseek(f, -positionNumber, 0) == -1) {
10620             DisplayError(_("Can't seek on position file"), 0);
10621             return FALSE;
10622         };
10623         pn = 1;
10624     } else {
10625         if (fseek(f, 0, 0) == -1) {
10626             if (f == lastLoadPositionFP ?
10627                 positionNumber == lastLoadPositionNumber + 1 :
10628                 positionNumber == 1) {
10629                 pn = 1;
10630             } else {
10631                 DisplayError(_("Can't seek on position file"), 0);
10632                 return FALSE;
10633             }
10634         }
10635     }
10636     /* See if this file is FEN or old-style xboard */
10637     if (fgets(line, MSG_SIZ, f) == NULL) {
10638         DisplayError(_("Position not found in file"), 0);
10639         return FALSE;
10640     }
10641     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10642     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10643
10644     if (pn >= 2) {
10645         if (fenMode || line[0] == '#') pn--;
10646         while (pn > 0) {
10647             /* skip positions before number pn */
10648             if (fgets(line, MSG_SIZ, f) == NULL) {
10649                 Reset(TRUE, TRUE);
10650                 DisplayError(_("Position not found in file"), 0);
10651                 return FALSE;
10652             }
10653             if (fenMode || line[0] == '#') pn--;
10654         }
10655     }
10656
10657     if (fenMode) {
10658         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10659             DisplayError(_("Bad FEN position in file"), 0);
10660             return FALSE;
10661         }
10662     } else {
10663         (void) fgets(line, MSG_SIZ, f);
10664         (void) fgets(line, MSG_SIZ, f);
10665
10666         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10667             (void) fgets(line, MSG_SIZ, f);
10668             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10669                 if (*p == ' ')
10670                   continue;
10671                 initial_position[i][j++] = CharToPiece(*p);
10672             }
10673         }
10674
10675         blackPlaysFirst = FALSE;
10676         if (!feof(f)) {
10677             (void) fgets(line, MSG_SIZ, f);
10678             if (strncmp(line, "black", strlen("black"))==0)
10679               blackPlaysFirst = TRUE;
10680         }
10681     }
10682     startedFromSetupPosition = TRUE;
10683
10684     SendToProgram("force\n", &first);
10685     CopyBoard(boards[0], initial_position);
10686     if (blackPlaysFirst) {
10687         currentMove = forwardMostMove = backwardMostMove = 1;
10688         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10689         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10690         CopyBoard(boards[1], initial_position);
10691         DisplayMessage("", _("Black to play"));
10692     } else {
10693         currentMove = forwardMostMove = backwardMostMove = 0;
10694         DisplayMessage("", _("White to play"));
10695     }
10696     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10697     SendBoard(&first, forwardMostMove);
10698     if (appData.debugMode) {
10699 int i, j;
10700   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10701   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10702         fprintf(debugFP, "Load Position\n");
10703     }
10704
10705     if (positionNumber > 1) {
10706       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10707         DisplayTitle(line);
10708     } else {
10709         DisplayTitle(title);
10710     }
10711     gameMode = EditGame;
10712     ModeHighlight();
10713     ResetClocks();
10714     timeRemaining[0][1] = whiteTimeRemaining;
10715     timeRemaining[1][1] = blackTimeRemaining;
10716     DrawPosition(FALSE, boards[currentMove]);
10717
10718     return TRUE;
10719 }
10720
10721
10722 void
10723 CopyPlayerNameIntoFileName(dest, src)
10724      char **dest, *src;
10725 {
10726     while (*src != NULLCHAR && *src != ',') {
10727         if (*src == ' ') {
10728             *(*dest)++ = '_';
10729             src++;
10730         } else {
10731             *(*dest)++ = *src++;
10732         }
10733     }
10734 }
10735
10736 char *DefaultFileName(ext)
10737      char *ext;
10738 {
10739     static char def[MSG_SIZ];
10740     char *p;
10741
10742     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10743         p = def;
10744         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10745         *p++ = '-';
10746         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10747         *p++ = '.';
10748         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10749     } else {
10750         def[0] = NULLCHAR;
10751     }
10752     return def;
10753 }
10754
10755 /* Save the current game to the given file */
10756 int
10757 SaveGameToFile(filename, append)
10758      char *filename;
10759      int append;
10760 {
10761     FILE *f;
10762     char buf[MSG_SIZ];
10763
10764     if (strcmp(filename, "-") == 0) {
10765         return SaveGame(stdout, 0, NULL);
10766     } else {
10767         f = fopen(filename, append ? "a" : "w");
10768         if (f == NULL) {
10769             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10770             DisplayError(buf, errno);
10771             return FALSE;
10772         } else {
10773             return SaveGame(f, 0, NULL);
10774         }
10775     }
10776 }
10777
10778 char *
10779 SavePart(str)
10780      char *str;
10781 {
10782     static char buf[MSG_SIZ];
10783     char *p;
10784
10785     p = strchr(str, ' ');
10786     if (p == NULL) return str;
10787     strncpy(buf, str, p - str);
10788     buf[p - str] = NULLCHAR;
10789     return buf;
10790 }
10791
10792 #define PGN_MAX_LINE 75
10793
10794 #define PGN_SIDE_WHITE  0
10795 #define PGN_SIDE_BLACK  1
10796
10797 /* [AS] */
10798 static int FindFirstMoveOutOfBook( int side )
10799 {
10800     int result = -1;
10801
10802     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10803         int index = backwardMostMove;
10804         int has_book_hit = 0;
10805
10806         if( (index % 2) != side ) {
10807             index++;
10808         }
10809
10810         while( index < forwardMostMove ) {
10811             /* Check to see if engine is in book */
10812             int depth = pvInfoList[index].depth;
10813             int score = pvInfoList[index].score;
10814             int in_book = 0;
10815
10816             if( depth <= 2 ) {
10817                 in_book = 1;
10818             }
10819             else if( score == 0 && depth == 63 ) {
10820                 in_book = 1; /* Zappa */
10821             }
10822             else if( score == 2 && depth == 99 ) {
10823                 in_book = 1; /* Abrok */
10824             }
10825
10826             has_book_hit += in_book;
10827
10828             if( ! in_book ) {
10829                 result = index;
10830
10831                 break;
10832             }
10833
10834             index += 2;
10835         }
10836     }
10837
10838     return result;
10839 }
10840
10841 /* [AS] */
10842 void GetOutOfBookInfo( char * buf )
10843 {
10844     int oob[2];
10845     int i;
10846     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10847
10848     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10849     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10850
10851     *buf = '\0';
10852
10853     if( oob[0] >= 0 || oob[1] >= 0 ) {
10854         for( i=0; i<2; i++ ) {
10855             int idx = oob[i];
10856
10857             if( idx >= 0 ) {
10858                 if( i > 0 && oob[0] >= 0 ) {
10859                     strcat( buf, "   " );
10860                 }
10861
10862                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10863                 sprintf( buf+strlen(buf), "%s%.2f",
10864                     pvInfoList[idx].score >= 0 ? "+" : "",
10865                     pvInfoList[idx].score / 100.0 );
10866             }
10867         }
10868     }
10869 }
10870
10871 /* Save game in PGN style and close the file */
10872 int
10873 SaveGamePGN(f)
10874      FILE *f;
10875 {
10876     int i, offset, linelen, newblock;
10877     time_t tm;
10878 //    char *movetext;
10879     char numtext[32];
10880     int movelen, numlen, blank;
10881     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10882
10883     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10884
10885     tm = time((time_t *) NULL);
10886
10887     PrintPGNTags(f, &gameInfo);
10888
10889     if (backwardMostMove > 0 || startedFromSetupPosition) {
10890         char *fen = PositionToFEN(backwardMostMove, NULL);
10891         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10892         fprintf(f, "\n{--------------\n");
10893         PrintPosition(f, backwardMostMove);
10894         fprintf(f, "--------------}\n");
10895         free(fen);
10896     }
10897     else {
10898         /* [AS] Out of book annotation */
10899         if( appData.saveOutOfBookInfo ) {
10900             char buf[64];
10901
10902             GetOutOfBookInfo( buf );
10903
10904             if( buf[0] != '\0' ) {
10905                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10906             }
10907         }
10908
10909         fprintf(f, "\n");
10910     }
10911
10912     i = backwardMostMove;
10913     linelen = 0;
10914     newblock = TRUE;
10915
10916     while (i < forwardMostMove) {
10917         /* Print comments preceding this move */
10918         if (commentList[i] != NULL) {
10919             if (linelen > 0) fprintf(f, "\n");
10920             fprintf(f, "%s", commentList[i]);
10921             linelen = 0;
10922             newblock = TRUE;
10923         }
10924
10925         /* Format move number */
10926         if ((i % 2) == 0)
10927           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10928         else
10929           if (newblock)
10930             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10931           else
10932             numtext[0] = NULLCHAR;
10933
10934         numlen = strlen(numtext);
10935         newblock = FALSE;
10936
10937         /* Print move number */
10938         blank = linelen > 0 && numlen > 0;
10939         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10940             fprintf(f, "\n");
10941             linelen = 0;
10942             blank = 0;
10943         }
10944         if (blank) {
10945             fprintf(f, " ");
10946             linelen++;
10947         }
10948         fprintf(f, "%s", numtext);
10949         linelen += numlen;
10950
10951         /* Get move */
10952         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10953         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10954
10955         /* Print move */
10956         blank = linelen > 0 && movelen > 0;
10957         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10958             fprintf(f, "\n");
10959             linelen = 0;
10960             blank = 0;
10961         }
10962         if (blank) {
10963             fprintf(f, " ");
10964             linelen++;
10965         }
10966         fprintf(f, "%s", move_buffer);
10967         linelen += movelen;
10968
10969         /* [AS] Add PV info if present */
10970         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10971             /* [HGM] add time */
10972             char buf[MSG_SIZ]; int seconds;
10973
10974             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10975
10976             if( seconds <= 0)
10977               buf[0] = 0;
10978             else
10979               if( seconds < 30 )
10980                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10981               else
10982                 {
10983                   seconds = (seconds + 4)/10; // round to full seconds
10984                   if( seconds < 60 )
10985                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10986                   else
10987                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10988                 }
10989
10990             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10991                       pvInfoList[i].score >= 0 ? "+" : "",
10992                       pvInfoList[i].score / 100.0,
10993                       pvInfoList[i].depth,
10994                       buf );
10995
10996             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10997
10998             /* Print score/depth */
10999             blank = linelen > 0 && movelen > 0;
11000             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11001                 fprintf(f, "\n");
11002                 linelen = 0;
11003                 blank = 0;
11004             }
11005             if (blank) {
11006                 fprintf(f, " ");
11007                 linelen++;
11008             }
11009             fprintf(f, "%s", move_buffer);
11010             linelen += movelen;
11011         }
11012
11013         i++;
11014     }
11015
11016     /* Start a new line */
11017     if (linelen > 0) fprintf(f, "\n");
11018
11019     /* Print comments after last move */
11020     if (commentList[i] != NULL) {
11021         fprintf(f, "%s\n", commentList[i]);
11022     }
11023
11024     /* Print result */
11025     if (gameInfo.resultDetails != NULL &&
11026         gameInfo.resultDetails[0] != NULLCHAR) {
11027         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11028                 PGNResult(gameInfo.result));
11029     } else {
11030         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11031     }
11032
11033     fclose(f);
11034     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11035     return TRUE;
11036 }
11037
11038 /* Save game in old style and close the file */
11039 int
11040 SaveGameOldStyle(f)
11041      FILE *f;
11042 {
11043     int i, offset;
11044     time_t tm;
11045
11046     tm = time((time_t *) NULL);
11047
11048     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11049     PrintOpponents(f);
11050
11051     if (backwardMostMove > 0 || startedFromSetupPosition) {
11052         fprintf(f, "\n[--------------\n");
11053         PrintPosition(f, backwardMostMove);
11054         fprintf(f, "--------------]\n");
11055     } else {
11056         fprintf(f, "\n");
11057     }
11058
11059     i = backwardMostMove;
11060     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11061
11062     while (i < forwardMostMove) {
11063         if (commentList[i] != NULL) {
11064             fprintf(f, "[%s]\n", commentList[i]);
11065         }
11066
11067         if ((i % 2) == 1) {
11068             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11069             i++;
11070         } else {
11071             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11072             i++;
11073             if (commentList[i] != NULL) {
11074                 fprintf(f, "\n");
11075                 continue;
11076             }
11077             if (i >= forwardMostMove) {
11078                 fprintf(f, "\n");
11079                 break;
11080             }
11081             fprintf(f, "%s\n", parseList[i]);
11082             i++;
11083         }
11084     }
11085
11086     if (commentList[i] != NULL) {
11087         fprintf(f, "[%s]\n", commentList[i]);
11088     }
11089
11090     /* This isn't really the old style, but it's close enough */
11091     if (gameInfo.resultDetails != NULL &&
11092         gameInfo.resultDetails[0] != NULLCHAR) {
11093         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11094                 gameInfo.resultDetails);
11095     } else {
11096         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11097     }
11098
11099     fclose(f);
11100     return TRUE;
11101 }
11102
11103 /* Save the current game to open file f and close the file */
11104 int
11105 SaveGame(f, dummy, dummy2)
11106      FILE *f;
11107      int dummy;
11108      char *dummy2;
11109 {
11110     if (gameMode == EditPosition) EditPositionDone(TRUE);
11111     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11112     if (appData.oldSaveStyle)
11113       return SaveGameOldStyle(f);
11114     else
11115       return SaveGamePGN(f);
11116 }
11117
11118 /* Save the current position to the given file */
11119 int
11120 SavePositionToFile(filename)
11121      char *filename;
11122 {
11123     FILE *f;
11124     char buf[MSG_SIZ];
11125
11126     if (strcmp(filename, "-") == 0) {
11127         return SavePosition(stdout, 0, NULL);
11128     } else {
11129         f = fopen(filename, "a");
11130         if (f == NULL) {
11131             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11132             DisplayError(buf, errno);
11133             return FALSE;
11134         } else {
11135             SavePosition(f, 0, NULL);
11136             return TRUE;
11137         }
11138     }
11139 }
11140
11141 /* Save the current position to the given open file and close the file */
11142 int
11143 SavePosition(f, dummy, dummy2)
11144      FILE *f;
11145      int dummy;
11146      char *dummy2;
11147 {
11148     time_t tm;
11149     char *fen;
11150
11151     if (gameMode == EditPosition) EditPositionDone(TRUE);
11152     if (appData.oldSaveStyle) {
11153         tm = time((time_t *) NULL);
11154
11155         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11156         PrintOpponents(f);
11157         fprintf(f, "[--------------\n");
11158         PrintPosition(f, currentMove);
11159         fprintf(f, "--------------]\n");
11160     } else {
11161         fen = PositionToFEN(currentMove, NULL);
11162         fprintf(f, "%s\n", fen);
11163         free(fen);
11164     }
11165     fclose(f);
11166     return TRUE;
11167 }
11168
11169 void
11170 ReloadCmailMsgEvent(unregister)
11171      int unregister;
11172 {
11173 #if !WIN32
11174     static char *inFilename = NULL;
11175     static char *outFilename;
11176     int i;
11177     struct stat inbuf, outbuf;
11178     int status;
11179
11180     /* Any registered moves are unregistered if unregister is set, */
11181     /* i.e. invoked by the signal handler */
11182     if (unregister) {
11183         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11184             cmailMoveRegistered[i] = FALSE;
11185             if (cmailCommentList[i] != NULL) {
11186                 free(cmailCommentList[i]);
11187                 cmailCommentList[i] = NULL;
11188             }
11189         }
11190         nCmailMovesRegistered = 0;
11191     }
11192
11193     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11194         cmailResult[i] = CMAIL_NOT_RESULT;
11195     }
11196     nCmailResults = 0;
11197
11198     if (inFilename == NULL) {
11199         /* Because the filenames are static they only get malloced once  */
11200         /* and they never get freed                                      */
11201         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11202         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11203
11204         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11205         sprintf(outFilename, "%s.out", appData.cmailGameName);
11206     }
11207
11208     status = stat(outFilename, &outbuf);
11209     if (status < 0) {
11210         cmailMailedMove = FALSE;
11211     } else {
11212         status = stat(inFilename, &inbuf);
11213         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11214     }
11215
11216     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11217        counts the games, notes how each one terminated, etc.
11218
11219        It would be nice to remove this kludge and instead gather all
11220        the information while building the game list.  (And to keep it
11221        in the game list nodes instead of having a bunch of fixed-size
11222        parallel arrays.)  Note this will require getting each game's
11223        termination from the PGN tags, as the game list builder does
11224        not process the game moves.  --mann
11225        */
11226     cmailMsgLoaded = TRUE;
11227     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11228
11229     /* Load first game in the file or popup game menu */
11230     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11231
11232 #endif /* !WIN32 */
11233     return;
11234 }
11235
11236 int
11237 RegisterMove()
11238 {
11239     FILE *f;
11240     char string[MSG_SIZ];
11241
11242     if (   cmailMailedMove
11243         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11244         return TRUE;            /* Allow free viewing  */
11245     }
11246
11247     /* Unregister move to ensure that we don't leave RegisterMove        */
11248     /* with the move registered when the conditions for registering no   */
11249     /* longer hold                                                       */
11250     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11251         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11252         nCmailMovesRegistered --;
11253
11254         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11255           {
11256               free(cmailCommentList[lastLoadGameNumber - 1]);
11257               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11258           }
11259     }
11260
11261     if (cmailOldMove == -1) {
11262         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11263         return FALSE;
11264     }
11265
11266     if (currentMove > cmailOldMove + 1) {
11267         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11268         return FALSE;
11269     }
11270
11271     if (currentMove < cmailOldMove) {
11272         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11273         return FALSE;
11274     }
11275
11276     if (forwardMostMove > currentMove) {
11277         /* Silently truncate extra moves */
11278         TruncateGame();
11279     }
11280
11281     if (   (currentMove == cmailOldMove + 1)
11282         || (   (currentMove == cmailOldMove)
11283             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11284                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11285         if (gameInfo.result != GameUnfinished) {
11286             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11287         }
11288
11289         if (commentList[currentMove] != NULL) {
11290             cmailCommentList[lastLoadGameNumber - 1]
11291               = StrSave(commentList[currentMove]);
11292         }
11293         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11294
11295         if (appData.debugMode)
11296           fprintf(debugFP, "Saving %s for game %d\n",
11297                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11298
11299         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11300
11301         f = fopen(string, "w");
11302         if (appData.oldSaveStyle) {
11303             SaveGameOldStyle(f); /* also closes the file */
11304
11305             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11306             f = fopen(string, "w");
11307             SavePosition(f, 0, NULL); /* also closes the file */
11308         } else {
11309             fprintf(f, "{--------------\n");
11310             PrintPosition(f, currentMove);
11311             fprintf(f, "--------------}\n\n");
11312
11313             SaveGame(f, 0, NULL); /* also closes the file*/
11314         }
11315
11316         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11317         nCmailMovesRegistered ++;
11318     } else if (nCmailGames == 1) {
11319         DisplayError(_("You have not made a move yet"), 0);
11320         return FALSE;
11321     }
11322
11323     return TRUE;
11324 }
11325
11326 void
11327 MailMoveEvent()
11328 {
11329 #if !WIN32
11330     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11331     FILE *commandOutput;
11332     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11333     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11334     int nBuffers;
11335     int i;
11336     int archived;
11337     char *arcDir;
11338
11339     if (! cmailMsgLoaded) {
11340         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11341         return;
11342     }
11343
11344     if (nCmailGames == nCmailResults) {
11345         DisplayError(_("No unfinished games"), 0);
11346         return;
11347     }
11348
11349 #if CMAIL_PROHIBIT_REMAIL
11350     if (cmailMailedMove) {
11351       snprintf(msg, MSG_SIZ, _("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);
11352         DisplayError(msg, 0);
11353         return;
11354     }
11355 #endif
11356
11357     if (! (cmailMailedMove || RegisterMove())) return;
11358
11359     if (   cmailMailedMove
11360         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11361       snprintf(string, MSG_SIZ, partCommandString,
11362                appData.debugMode ? " -v" : "", appData.cmailGameName);
11363         commandOutput = popen(string, "r");
11364
11365         if (commandOutput == NULL) {
11366             DisplayError(_("Failed to invoke cmail"), 0);
11367         } else {
11368             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11369                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11370             }
11371             if (nBuffers > 1) {
11372                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11373                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11374                 nBytes = MSG_SIZ - 1;
11375             } else {
11376                 (void) memcpy(msg, buffer, nBytes);
11377             }
11378             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11379
11380             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11381                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11382
11383                 archived = TRUE;
11384                 for (i = 0; i < nCmailGames; i ++) {
11385                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11386                         archived = FALSE;
11387                     }
11388                 }
11389                 if (   archived
11390                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11391                         != NULL)) {
11392                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11393                            arcDir,
11394                            appData.cmailGameName,
11395                            gameInfo.date);
11396                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11397                     cmailMsgLoaded = FALSE;
11398                 }
11399             }
11400
11401             DisplayInformation(msg);
11402             pclose(commandOutput);
11403         }
11404     } else {
11405         if ((*cmailMsg) != '\0') {
11406             DisplayInformation(cmailMsg);
11407         }
11408     }
11409
11410     return;
11411 #endif /* !WIN32 */
11412 }
11413
11414 char *
11415 CmailMsg()
11416 {
11417 #if WIN32
11418     return NULL;
11419 #else
11420     int  prependComma = 0;
11421     char number[5];
11422     char string[MSG_SIZ];       /* Space for game-list */
11423     int  i;
11424
11425     if (!cmailMsgLoaded) return "";
11426
11427     if (cmailMailedMove) {
11428       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11429     } else {
11430         /* Create a list of games left */
11431       snprintf(string, MSG_SIZ, "[");
11432         for (i = 0; i < nCmailGames; i ++) {
11433             if (! (   cmailMoveRegistered[i]
11434                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11435                 if (prependComma) {
11436                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11437                 } else {
11438                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11439                     prependComma = 1;
11440                 }
11441
11442                 strcat(string, number);
11443             }
11444         }
11445         strcat(string, "]");
11446
11447         if (nCmailMovesRegistered + nCmailResults == 0) {
11448             switch (nCmailGames) {
11449               case 1:
11450                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11451                 break;
11452
11453               case 2:
11454                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11455                 break;
11456
11457               default:
11458                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11459                          nCmailGames);
11460                 break;
11461             }
11462         } else {
11463             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11464               case 1:
11465                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11466                          string);
11467                 break;
11468
11469               case 0:
11470                 if (nCmailResults == nCmailGames) {
11471                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11472                 } else {
11473                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11474                 }
11475                 break;
11476
11477               default:
11478                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11479                          string);
11480             }
11481         }
11482     }
11483     return cmailMsg;
11484 #endif /* WIN32 */
11485 }
11486
11487 void
11488 ResetGameEvent()
11489 {
11490     if (gameMode == Training)
11491       SetTrainingModeOff();
11492
11493     Reset(TRUE, TRUE);
11494     cmailMsgLoaded = FALSE;
11495     if (appData.icsActive) {
11496       SendToICS(ics_prefix);
11497       SendToICS("refresh\n");
11498     }
11499 }
11500
11501 void
11502 ExitEvent(status)
11503      int status;
11504 {
11505     exiting++;
11506     if (exiting > 2) {
11507       /* Give up on clean exit */
11508       exit(status);
11509     }
11510     if (exiting > 1) {
11511       /* Keep trying for clean exit */
11512       return;
11513     }
11514
11515     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11516
11517     if (telnetISR != NULL) {
11518       RemoveInputSource(telnetISR);
11519     }
11520     if (icsPR != NoProc) {
11521       DestroyChildProcess(icsPR, TRUE);
11522     }
11523
11524     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11525     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11526
11527     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11528     /* make sure this other one finishes before killing it!                  */
11529     if(endingGame) { int count = 0;
11530         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11531         while(endingGame && count++ < 10) DoSleep(1);
11532         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11533     }
11534
11535     /* Kill off chess programs */
11536     if (first.pr != NoProc) {
11537         ExitAnalyzeMode();
11538
11539         DoSleep( appData.delayBeforeQuit );
11540         SendToProgram("quit\n", &first);
11541         DoSleep( appData.delayAfterQuit );
11542         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11543     }
11544     if (second.pr != NoProc) {
11545         DoSleep( appData.delayBeforeQuit );
11546         SendToProgram("quit\n", &second);
11547         DoSleep( appData.delayAfterQuit );
11548         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11549     }
11550     if (first.isr != NULL) {
11551         RemoveInputSource(first.isr);
11552     }
11553     if (second.isr != NULL) {
11554         RemoveInputSource(second.isr);
11555     }
11556
11557     ShutDownFrontEnd();
11558     exit(status);
11559 }
11560
11561 void
11562 PauseEvent()
11563 {
11564     if (appData.debugMode)
11565         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11566     if (pausing) {
11567         pausing = FALSE;
11568         ModeHighlight();
11569         if (gameMode == MachinePlaysWhite ||
11570             gameMode == MachinePlaysBlack) {
11571             StartClocks();
11572         } else {
11573             DisplayBothClocks();
11574         }
11575         if (gameMode == PlayFromGameFile) {
11576             if (appData.timeDelay >= 0)
11577                 AutoPlayGameLoop();
11578         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11579             Reset(FALSE, TRUE);
11580             SendToICS(ics_prefix);
11581             SendToICS("refresh\n");
11582         } else if (currentMove < forwardMostMove) {
11583             ForwardInner(forwardMostMove);
11584         }
11585         pauseExamInvalid = FALSE;
11586     } else {
11587         switch (gameMode) {
11588           default:
11589             return;
11590           case IcsExamining:
11591             pauseExamForwardMostMove = forwardMostMove;
11592             pauseExamInvalid = FALSE;
11593             /* fall through */
11594           case IcsObserving:
11595           case IcsPlayingWhite:
11596           case IcsPlayingBlack:
11597             pausing = TRUE;
11598             ModeHighlight();
11599             return;
11600           case PlayFromGameFile:
11601             (void) StopLoadGameTimer();
11602             pausing = TRUE;
11603             ModeHighlight();
11604             break;
11605           case BeginningOfGame:
11606             if (appData.icsActive) return;
11607             /* else fall through */
11608           case MachinePlaysWhite:
11609           case MachinePlaysBlack:
11610           case TwoMachinesPlay:
11611             if (forwardMostMove == 0)
11612               return;           /* don't pause if no one has moved */
11613             if ((gameMode == MachinePlaysWhite &&
11614                  !WhiteOnMove(forwardMostMove)) ||
11615                 (gameMode == MachinePlaysBlack &&
11616                  WhiteOnMove(forwardMostMove))) {
11617                 StopClocks();
11618             }
11619             pausing = TRUE;
11620             ModeHighlight();
11621             break;
11622         }
11623     }
11624 }
11625
11626 void
11627 EditCommentEvent()
11628 {
11629     char title[MSG_SIZ];
11630
11631     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11632       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11633     } else {
11634       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11635                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11636                parseList[currentMove - 1]);
11637     }
11638
11639     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11640 }
11641
11642
11643 void
11644 EditTagsEvent()
11645 {
11646     char *tags = PGNTags(&gameInfo);
11647     EditTagsPopUp(tags, NULL);
11648     free(tags);
11649 }
11650
11651 void
11652 AnalyzeModeEvent()
11653 {
11654     if (appData.noChessProgram || gameMode == AnalyzeMode)
11655       return;
11656
11657     if (gameMode != AnalyzeFile) {
11658         if (!appData.icsEngineAnalyze) {
11659                EditGameEvent();
11660                if (gameMode != EditGame) return;
11661         }
11662         ResurrectChessProgram();
11663         SendToProgram("analyze\n", &first);
11664         first.analyzing = TRUE;
11665         /*first.maybeThinking = TRUE;*/
11666         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11667         EngineOutputPopUp();
11668     }
11669     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11670     pausing = FALSE;
11671     ModeHighlight();
11672     SetGameInfo();
11673
11674     StartAnalysisClock();
11675     GetTimeMark(&lastNodeCountTime);
11676     lastNodeCount = 0;
11677 }
11678
11679 void
11680 AnalyzeFileEvent()
11681 {
11682     if (appData.noChessProgram || gameMode == AnalyzeFile)
11683       return;
11684
11685     if (gameMode != AnalyzeMode) {
11686         EditGameEvent();
11687         if (gameMode != EditGame) return;
11688         ResurrectChessProgram();
11689         SendToProgram("analyze\n", &first);
11690         first.analyzing = TRUE;
11691         /*first.maybeThinking = TRUE;*/
11692         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11693         EngineOutputPopUp();
11694     }
11695     gameMode = AnalyzeFile;
11696     pausing = FALSE;
11697     ModeHighlight();
11698     SetGameInfo();
11699
11700     StartAnalysisClock();
11701     GetTimeMark(&lastNodeCountTime);
11702     lastNodeCount = 0;
11703 }
11704
11705 void
11706 MachineWhiteEvent()
11707 {
11708     char buf[MSG_SIZ];
11709     char *bookHit = NULL;
11710
11711     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11712       return;
11713
11714
11715     if (gameMode == PlayFromGameFile ||
11716         gameMode == TwoMachinesPlay  ||
11717         gameMode == Training         ||
11718         gameMode == AnalyzeMode      ||
11719         gameMode == EndOfGame)
11720         EditGameEvent();
11721
11722     if (gameMode == EditPosition)
11723         EditPositionDone(TRUE);
11724
11725     if (!WhiteOnMove(currentMove)) {
11726         DisplayError(_("It is not White's turn"), 0);
11727         return;
11728     }
11729
11730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11731       ExitAnalyzeMode();
11732
11733     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11734         gameMode == AnalyzeFile)
11735         TruncateGame();
11736
11737     ResurrectChessProgram();    /* in case it isn't running */
11738     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11739         gameMode = MachinePlaysWhite;
11740         ResetClocks();
11741     } else
11742     gameMode = MachinePlaysWhite;
11743     pausing = FALSE;
11744     ModeHighlight();
11745     SetGameInfo();
11746     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11747     DisplayTitle(buf);
11748     if (first.sendName) {
11749       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11750       SendToProgram(buf, &first);
11751     }
11752     if (first.sendTime) {
11753       if (first.useColors) {
11754         SendToProgram("black\n", &first); /*gnu kludge*/
11755       }
11756       SendTimeRemaining(&first, TRUE);
11757     }
11758     if (first.useColors) {
11759       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11760     }
11761     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11762     SetMachineThinkingEnables();
11763     first.maybeThinking = TRUE;
11764     StartClocks();
11765     firstMove = FALSE;
11766
11767     if (appData.autoFlipView && !flipView) {
11768       flipView = !flipView;
11769       DrawPosition(FALSE, NULL);
11770       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11771     }
11772
11773     if(bookHit) { // [HGM] book: simulate book reply
11774         static char bookMove[MSG_SIZ]; // a bit generous?
11775
11776         programStats.nodes = programStats.depth = programStats.time =
11777         programStats.score = programStats.got_only_move = 0;
11778         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11779
11780         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11781         strcat(bookMove, bookHit);
11782         HandleMachineMove(bookMove, &first);
11783     }
11784 }
11785
11786 void
11787 MachineBlackEvent()
11788 {
11789   char buf[MSG_SIZ];
11790   char *bookHit = NULL;
11791
11792     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11793         return;
11794
11795
11796     if (gameMode == PlayFromGameFile ||
11797         gameMode == TwoMachinesPlay  ||
11798         gameMode == Training         ||
11799         gameMode == AnalyzeMode      ||
11800         gameMode == EndOfGame)
11801         EditGameEvent();
11802
11803     if (gameMode == EditPosition)
11804         EditPositionDone(TRUE);
11805
11806     if (WhiteOnMove(currentMove)) {
11807         DisplayError(_("It is not Black's turn"), 0);
11808         return;
11809     }
11810
11811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11812       ExitAnalyzeMode();
11813
11814     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11815         gameMode == AnalyzeFile)
11816         TruncateGame();
11817
11818     ResurrectChessProgram();    /* in case it isn't running */
11819     gameMode = MachinePlaysBlack;
11820     pausing = FALSE;
11821     ModeHighlight();
11822     SetGameInfo();
11823     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11824     DisplayTitle(buf);
11825     if (first.sendName) {
11826       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11827       SendToProgram(buf, &first);
11828     }
11829     if (first.sendTime) {
11830       if (first.useColors) {
11831         SendToProgram("white\n", &first); /*gnu kludge*/
11832       }
11833       SendTimeRemaining(&first, FALSE);
11834     }
11835     if (first.useColors) {
11836       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11837     }
11838     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11839     SetMachineThinkingEnables();
11840     first.maybeThinking = TRUE;
11841     StartClocks();
11842
11843     if (appData.autoFlipView && flipView) {
11844       flipView = !flipView;
11845       DrawPosition(FALSE, NULL);
11846       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11847     }
11848     if(bookHit) { // [HGM] book: simulate book reply
11849         static char bookMove[MSG_SIZ]; // a bit generous?
11850
11851         programStats.nodes = programStats.depth = programStats.time =
11852         programStats.score = programStats.got_only_move = 0;
11853         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11854
11855         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11856         strcat(bookMove, bookHit);
11857         HandleMachineMove(bookMove, &first);
11858     }
11859 }
11860
11861
11862 void
11863 DisplayTwoMachinesTitle()
11864 {
11865     char buf[MSG_SIZ];
11866     if (appData.matchGames > 0) {
11867         if (first.twoMachinesColor[0] == 'w') {
11868           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11869                    gameInfo.white, gameInfo.black,
11870                    first.matchWins, second.matchWins,
11871                    matchGame - 1 - (first.matchWins + second.matchWins));
11872         } else {
11873           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11874                    gameInfo.white, gameInfo.black,
11875                    second.matchWins, first.matchWins,
11876                    matchGame - 1 - (first.matchWins + second.matchWins));
11877         }
11878     } else {
11879       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11880     }
11881     DisplayTitle(buf);
11882 }
11883
11884 void
11885 SettingsMenuIfReady()
11886 {
11887   if (second.lastPing != second.lastPong) {
11888     DisplayMessage("", _("Waiting for second chess program"));
11889     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11890     return;
11891   }
11892   ThawUI();
11893   DisplayMessage("", "");
11894   SettingsPopUp(&second);
11895 }
11896
11897 int
11898 WaitForSecond(DelayedEventCallback retry)
11899 {
11900     if (second.pr == NULL) {
11901         StartChessProgram(&second);
11902         if (second.protocolVersion == 1) {
11903           retry();
11904         } else {
11905           /* kludge: allow timeout for initial "feature" command */
11906           FreezeUI();
11907           DisplayMessage("", _("Starting second chess program"));
11908           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11909         }
11910         return 1;
11911     }
11912     return 0;
11913 }
11914
11915 void
11916 TwoMachinesEvent P((void))
11917 {
11918     int i;
11919     char buf[MSG_SIZ];
11920     ChessProgramState *onmove;
11921     char *bookHit = NULL;
11922
11923     if (appData.noChessProgram) return;
11924
11925     switch (gameMode) {
11926       case TwoMachinesPlay:
11927         return;
11928       case MachinePlaysWhite:
11929       case MachinePlaysBlack:
11930         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11931             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11932             return;
11933         }
11934         /* fall through */
11935       case BeginningOfGame:
11936       case PlayFromGameFile:
11937       case EndOfGame:
11938         EditGameEvent();
11939         if (gameMode != EditGame) return;
11940         break;
11941       case EditPosition:
11942         EditPositionDone(TRUE);
11943         break;
11944       case AnalyzeMode:
11945       case AnalyzeFile:
11946         ExitAnalyzeMode();
11947         break;
11948       case EditGame:
11949       default:
11950         break;
11951     }
11952
11953 //    forwardMostMove = currentMove;
11954     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11955     ResurrectChessProgram();    /* in case first program isn't running */
11956
11957     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11958     DisplayMessage("", "");
11959     InitChessProgram(&second, FALSE);
11960     SendToProgram("force\n", &second);
11961     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11962       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11963       return;
11964     }
11965     if (startedFromSetupPosition) {
11966         SendBoard(&second, backwardMostMove);
11967     if (appData.debugMode) {
11968         fprintf(debugFP, "Two Machines\n");
11969     }
11970     }
11971     for (i = backwardMostMove; i < forwardMostMove; i++) {
11972         SendMoveToProgram(i, &second);
11973     }
11974
11975     gameMode = TwoMachinesPlay;
11976     pausing = FALSE;
11977     ModeHighlight();
11978     SetGameInfo();
11979     DisplayTwoMachinesTitle();
11980     firstMove = TRUE;
11981     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11982         onmove = &first;
11983     } else {
11984         onmove = &second;
11985     }
11986
11987     SendToProgram(first.computerString, &first);
11988     if (first.sendName) {
11989       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11990       SendToProgram(buf, &first);
11991     }
11992     SendToProgram(second.computerString, &second);
11993     if (second.sendName) {
11994       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11995       SendToProgram(buf, &second);
11996     }
11997
11998     ResetClocks();
11999     if (!first.sendTime || !second.sendTime) {
12000         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12001         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12002     }
12003     if (onmove->sendTime) {
12004       if (onmove->useColors) {
12005         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12006       }
12007       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12008     }
12009     if (onmove->useColors) {
12010       SendToProgram(onmove->twoMachinesColor, onmove);
12011     }
12012     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12013 //    SendToProgram("go\n", onmove);
12014     onmove->maybeThinking = TRUE;
12015     SetMachineThinkingEnables();
12016
12017     StartClocks();
12018
12019     if(bookHit) { // [HGM] book: simulate book reply
12020         static char bookMove[MSG_SIZ]; // a bit generous?
12021
12022         programStats.nodes = programStats.depth = programStats.time =
12023         programStats.score = programStats.got_only_move = 0;
12024         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12025
12026         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12027         strcat(bookMove, bookHit);
12028         savedMessage = bookMove; // args for deferred call
12029         savedState = onmove;
12030         ScheduleDelayedEvent(DeferredBookMove, 1);
12031     }
12032 }
12033
12034 void
12035 TrainingEvent()
12036 {
12037     if (gameMode == Training) {
12038       SetTrainingModeOff();
12039       gameMode = PlayFromGameFile;
12040       DisplayMessage("", _("Training mode off"));
12041     } else {
12042       gameMode = Training;
12043       animateTraining = appData.animate;
12044
12045       /* make sure we are not already at the end of the game */
12046       if (currentMove < forwardMostMove) {
12047         SetTrainingModeOn();
12048         DisplayMessage("", _("Training mode on"));
12049       } else {
12050         gameMode = PlayFromGameFile;
12051         DisplayError(_("Already at end of game"), 0);
12052       }
12053     }
12054     ModeHighlight();
12055 }
12056
12057 void
12058 IcsClientEvent()
12059 {
12060     if (!appData.icsActive) return;
12061     switch (gameMode) {
12062       case IcsPlayingWhite:
12063       case IcsPlayingBlack:
12064       case IcsObserving:
12065       case IcsIdle:
12066       case BeginningOfGame:
12067       case IcsExamining:
12068         return;
12069
12070       case EditGame:
12071         break;
12072
12073       case EditPosition:
12074         EditPositionDone(TRUE);
12075         break;
12076
12077       case AnalyzeMode:
12078       case AnalyzeFile:
12079         ExitAnalyzeMode();
12080         break;
12081
12082       default:
12083         EditGameEvent();
12084         break;
12085     }
12086
12087     gameMode = IcsIdle;
12088     ModeHighlight();
12089     return;
12090 }
12091
12092
12093 void
12094 EditGameEvent()
12095 {
12096     int i;
12097
12098     switch (gameMode) {
12099       case Training:
12100         SetTrainingModeOff();
12101         break;
12102       case MachinePlaysWhite:
12103       case MachinePlaysBlack:
12104       case BeginningOfGame:
12105         SendToProgram("force\n", &first);
12106         SetUserThinkingEnables();
12107         break;
12108       case PlayFromGameFile:
12109         (void) StopLoadGameTimer();
12110         if (gameFileFP != NULL) {
12111             gameFileFP = NULL;
12112         }
12113         break;
12114       case EditPosition:
12115         EditPositionDone(TRUE);
12116         break;
12117       case AnalyzeMode:
12118       case AnalyzeFile:
12119         ExitAnalyzeMode();
12120         SendToProgram("force\n", &first);
12121         break;
12122       case TwoMachinesPlay:
12123         GameEnds(EndOfFile, NULL, GE_PLAYER);
12124         ResurrectChessProgram();
12125         SetUserThinkingEnables();
12126         break;
12127       case EndOfGame:
12128         ResurrectChessProgram();
12129         break;
12130       case IcsPlayingBlack:
12131       case IcsPlayingWhite:
12132         DisplayError(_("Warning: You are still playing a game"), 0);
12133         break;
12134       case IcsObserving:
12135         DisplayError(_("Warning: You are still observing a game"), 0);
12136         break;
12137       case IcsExamining:
12138         DisplayError(_("Warning: You are still examining a game"), 0);
12139         break;
12140       case IcsIdle:
12141         break;
12142       case EditGame:
12143       default:
12144         return;
12145     }
12146
12147     pausing = FALSE;
12148     StopClocks();
12149     first.offeredDraw = second.offeredDraw = 0;
12150
12151     if (gameMode == PlayFromGameFile) {
12152         whiteTimeRemaining = timeRemaining[0][currentMove];
12153         blackTimeRemaining = timeRemaining[1][currentMove];
12154         DisplayTitle("");
12155     }
12156
12157     if (gameMode == MachinePlaysWhite ||
12158         gameMode == MachinePlaysBlack ||
12159         gameMode == TwoMachinesPlay ||
12160         gameMode == EndOfGame) {
12161         i = forwardMostMove;
12162         while (i > currentMove) {
12163             SendToProgram("undo\n", &first);
12164             i--;
12165         }
12166         whiteTimeRemaining = timeRemaining[0][currentMove];
12167         blackTimeRemaining = timeRemaining[1][currentMove];
12168         DisplayBothClocks();
12169         if (whiteFlag || blackFlag) {
12170             whiteFlag = blackFlag = 0;
12171         }
12172         DisplayTitle("");
12173     }
12174
12175     gameMode = EditGame;
12176     ModeHighlight();
12177     SetGameInfo();
12178 }
12179
12180
12181 void
12182 EditPositionEvent()
12183 {
12184     if (gameMode == EditPosition) {
12185         EditGameEvent();
12186         return;
12187     }
12188
12189     EditGameEvent();
12190     if (gameMode != EditGame) return;
12191
12192     gameMode = EditPosition;
12193     ModeHighlight();
12194     SetGameInfo();
12195     if (currentMove > 0)
12196       CopyBoard(boards[0], boards[currentMove]);
12197
12198     blackPlaysFirst = !WhiteOnMove(currentMove);
12199     ResetClocks();
12200     currentMove = forwardMostMove = backwardMostMove = 0;
12201     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12202     DisplayMove(-1);
12203 }
12204
12205 void
12206 ExitAnalyzeMode()
12207 {
12208     /* [DM] icsEngineAnalyze - possible call from other functions */
12209     if (appData.icsEngineAnalyze) {
12210         appData.icsEngineAnalyze = FALSE;
12211
12212         DisplayMessage("",_("Close ICS engine analyze..."));
12213     }
12214     if (first.analysisSupport && first.analyzing) {
12215       SendToProgram("exit\n", &first);
12216       first.analyzing = FALSE;
12217     }
12218     thinkOutput[0] = NULLCHAR;
12219 }
12220
12221 void
12222 EditPositionDone(Boolean fakeRights)
12223 {
12224     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12225
12226     startedFromSetupPosition = TRUE;
12227     InitChessProgram(&first, FALSE);
12228     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12229       boards[0][EP_STATUS] = EP_NONE;
12230       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12231     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12232         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12233         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12234       } else boards[0][CASTLING][2] = NoRights;
12235     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12236         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12237         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12238       } else boards[0][CASTLING][5] = NoRights;
12239     }
12240     SendToProgram("force\n", &first);
12241     if (blackPlaysFirst) {
12242         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12243         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12244         currentMove = forwardMostMove = backwardMostMove = 1;
12245         CopyBoard(boards[1], boards[0]);
12246     } else {
12247         currentMove = forwardMostMove = backwardMostMove = 0;
12248     }
12249     SendBoard(&first, forwardMostMove);
12250     if (appData.debugMode) {
12251         fprintf(debugFP, "EditPosDone\n");
12252     }
12253     DisplayTitle("");
12254     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12255     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12256     gameMode = EditGame;
12257     ModeHighlight();
12258     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12259     ClearHighlights(); /* [AS] */
12260 }
12261
12262 /* Pause for `ms' milliseconds */
12263 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12264 void
12265 TimeDelay(ms)
12266      long ms;
12267 {
12268     TimeMark m1, m2;
12269
12270     GetTimeMark(&m1);
12271     do {
12272         GetTimeMark(&m2);
12273     } while (SubtractTimeMarks(&m2, &m1) < ms);
12274 }
12275
12276 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12277 void
12278 SendMultiLineToICS(buf)
12279      char *buf;
12280 {
12281     char temp[MSG_SIZ+1], *p;
12282     int len;
12283
12284     len = strlen(buf);
12285     if (len > MSG_SIZ)
12286       len = MSG_SIZ;
12287
12288     strncpy(temp, buf, len);
12289     temp[len] = 0;
12290
12291     p = temp;
12292     while (*p) {
12293         if (*p == '\n' || *p == '\r')
12294           *p = ' ';
12295         ++p;
12296     }
12297
12298     strcat(temp, "\n");
12299     SendToICS(temp);
12300     SendToPlayer(temp, strlen(temp));
12301 }
12302
12303 void
12304 SetWhiteToPlayEvent()
12305 {
12306     if (gameMode == EditPosition) {
12307         blackPlaysFirst = FALSE;
12308         DisplayBothClocks();    /* works because currentMove is 0 */
12309     } else if (gameMode == IcsExamining) {
12310         SendToICS(ics_prefix);
12311         SendToICS("tomove white\n");
12312     }
12313 }
12314
12315 void
12316 SetBlackToPlayEvent()
12317 {
12318     if (gameMode == EditPosition) {
12319         blackPlaysFirst = TRUE;
12320         currentMove = 1;        /* kludge */
12321         DisplayBothClocks();
12322         currentMove = 0;
12323     } else if (gameMode == IcsExamining) {
12324         SendToICS(ics_prefix);
12325         SendToICS("tomove black\n");
12326     }
12327 }
12328
12329 void
12330 EditPositionMenuEvent(selection, x, y)
12331      ChessSquare selection;
12332      int x, y;
12333 {
12334     char buf[MSG_SIZ];
12335     ChessSquare piece = boards[0][y][x];
12336
12337     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12338
12339     switch (selection) {
12340       case ClearBoard:
12341         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12342             SendToICS(ics_prefix);
12343             SendToICS("bsetup clear\n");
12344         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12345             SendToICS(ics_prefix);
12346             SendToICS("clearboard\n");
12347         } else {
12348             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12349                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12350                 for (y = 0; y < BOARD_HEIGHT; y++) {
12351                     if (gameMode == IcsExamining) {
12352                         if (boards[currentMove][y][x] != EmptySquare) {
12353                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12354                                     AAA + x, ONE + y);
12355                             SendToICS(buf);
12356                         }
12357                     } else {
12358                         boards[0][y][x] = p;
12359                     }
12360                 }
12361             }
12362         }
12363         if (gameMode == EditPosition) {
12364             DrawPosition(FALSE, boards[0]);
12365         }
12366         break;
12367
12368       case WhitePlay:
12369         SetWhiteToPlayEvent();
12370         break;
12371
12372       case BlackPlay:
12373         SetBlackToPlayEvent();
12374         break;
12375
12376       case EmptySquare:
12377         if (gameMode == IcsExamining) {
12378             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12379             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12380             SendToICS(buf);
12381         } else {
12382             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12383                 if(x == BOARD_LEFT-2) {
12384                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12385                     boards[0][y][1] = 0;
12386                 } else
12387                 if(x == BOARD_RGHT+1) {
12388                     if(y >= gameInfo.holdingsSize) break;
12389                     boards[0][y][BOARD_WIDTH-2] = 0;
12390                 } else break;
12391             }
12392             boards[0][y][x] = EmptySquare;
12393             DrawPosition(FALSE, boards[0]);
12394         }
12395         break;
12396
12397       case PromotePiece:
12398         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12399            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12400             selection = (ChessSquare) (PROMOTED piece);
12401         } else if(piece == EmptySquare) selection = WhiteSilver;
12402         else selection = (ChessSquare)((int)piece - 1);
12403         goto defaultlabel;
12404
12405       case DemotePiece:
12406         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12407            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12408             selection = (ChessSquare) (DEMOTED piece);
12409         } else if(piece == EmptySquare) selection = BlackSilver;
12410         else selection = (ChessSquare)((int)piece + 1);
12411         goto defaultlabel;
12412
12413       case WhiteQueen:
12414       case BlackQueen:
12415         if(gameInfo.variant == VariantShatranj ||
12416            gameInfo.variant == VariantXiangqi  ||
12417            gameInfo.variant == VariantCourier  ||
12418            gameInfo.variant == VariantMakruk     )
12419             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12420         goto defaultlabel;
12421
12422       case WhiteKing:
12423       case BlackKing:
12424         if(gameInfo.variant == VariantXiangqi)
12425             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12426         if(gameInfo.variant == VariantKnightmate)
12427             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12428       default:
12429         defaultlabel:
12430         if (gameMode == IcsExamining) {
12431             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12432             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12433                      PieceToChar(selection), AAA + x, ONE + y);
12434             SendToICS(buf);
12435         } else {
12436             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12437                 int n;
12438                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12439                     n = PieceToNumber(selection - BlackPawn);
12440                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12441                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12442                     boards[0][BOARD_HEIGHT-1-n][1]++;
12443                 } else
12444                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12445                     n = PieceToNumber(selection);
12446                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12447                     boards[0][n][BOARD_WIDTH-1] = selection;
12448                     boards[0][n][BOARD_WIDTH-2]++;
12449                 }
12450             } else
12451             boards[0][y][x] = selection;
12452             DrawPosition(TRUE, boards[0]);
12453         }
12454         break;
12455     }
12456 }
12457
12458
12459 void
12460 DropMenuEvent(selection, x, y)
12461      ChessSquare selection;
12462      int x, y;
12463 {
12464     ChessMove moveType;
12465
12466     switch (gameMode) {
12467       case IcsPlayingWhite:
12468       case MachinePlaysBlack:
12469         if (!WhiteOnMove(currentMove)) {
12470             DisplayMoveError(_("It is Black's turn"));
12471             return;
12472         }
12473         moveType = WhiteDrop;
12474         break;
12475       case IcsPlayingBlack:
12476       case MachinePlaysWhite:
12477         if (WhiteOnMove(currentMove)) {
12478             DisplayMoveError(_("It is White's turn"));
12479             return;
12480         }
12481         moveType = BlackDrop;
12482         break;
12483       case EditGame:
12484         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12485         break;
12486       default:
12487         return;
12488     }
12489
12490     if (moveType == BlackDrop && selection < BlackPawn) {
12491       selection = (ChessSquare) ((int) selection
12492                                  + (int) BlackPawn - (int) WhitePawn);
12493     }
12494     if (boards[currentMove][y][x] != EmptySquare) {
12495         DisplayMoveError(_("That square is occupied"));
12496         return;
12497     }
12498
12499     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12500 }
12501
12502 void
12503 AcceptEvent()
12504 {
12505     /* Accept a pending offer of any kind from opponent */
12506
12507     if (appData.icsActive) {
12508         SendToICS(ics_prefix);
12509         SendToICS("accept\n");
12510     } else if (cmailMsgLoaded) {
12511         if (currentMove == cmailOldMove &&
12512             commentList[cmailOldMove] != NULL &&
12513             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12514                    "Black offers a draw" : "White offers a draw")) {
12515             TruncateGame();
12516             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12517             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12518         } else {
12519             DisplayError(_("There is no pending offer on this move"), 0);
12520             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12521         }
12522     } else {
12523         /* Not used for offers from chess program */
12524     }
12525 }
12526
12527 void
12528 DeclineEvent()
12529 {
12530     /* Decline a pending offer of any kind from opponent */
12531
12532     if (appData.icsActive) {
12533         SendToICS(ics_prefix);
12534         SendToICS("decline\n");
12535     } else if (cmailMsgLoaded) {
12536         if (currentMove == cmailOldMove &&
12537             commentList[cmailOldMove] != NULL &&
12538             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12539                    "Black offers a draw" : "White offers a draw")) {
12540 #ifdef NOTDEF
12541             AppendComment(cmailOldMove, "Draw declined", TRUE);
12542             DisplayComment(cmailOldMove - 1, "Draw declined");
12543 #endif /*NOTDEF*/
12544         } else {
12545             DisplayError(_("There is no pending offer on this move"), 0);
12546         }
12547     } else {
12548         /* Not used for offers from chess program */
12549     }
12550 }
12551
12552 void
12553 RematchEvent()
12554 {
12555     /* Issue ICS rematch command */
12556     if (appData.icsActive) {
12557         SendToICS(ics_prefix);
12558         SendToICS("rematch\n");
12559     }
12560 }
12561
12562 void
12563 CallFlagEvent()
12564 {
12565     /* Call your opponent's flag (claim a win on time) */
12566     if (appData.icsActive) {
12567         SendToICS(ics_prefix);
12568         SendToICS("flag\n");
12569     } else {
12570         switch (gameMode) {
12571           default:
12572             return;
12573           case MachinePlaysWhite:
12574             if (whiteFlag) {
12575                 if (blackFlag)
12576                   GameEnds(GameIsDrawn, "Both players ran out of time",
12577                            GE_PLAYER);
12578                 else
12579                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12580             } else {
12581                 DisplayError(_("Your opponent is not out of time"), 0);
12582             }
12583             break;
12584           case MachinePlaysBlack:
12585             if (blackFlag) {
12586                 if (whiteFlag)
12587                   GameEnds(GameIsDrawn, "Both players ran out of time",
12588                            GE_PLAYER);
12589                 else
12590                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12591             } else {
12592                 DisplayError(_("Your opponent is not out of time"), 0);
12593             }
12594             break;
12595         }
12596     }
12597 }
12598
12599 void
12600 DrawEvent()
12601 {
12602     /* Offer draw or accept pending draw offer from opponent */
12603
12604     if (appData.icsActive) {
12605         /* Note: tournament rules require draw offers to be
12606            made after you make your move but before you punch
12607            your clock.  Currently ICS doesn't let you do that;
12608            instead, you immediately punch your clock after making
12609            a move, but you can offer a draw at any time. */
12610
12611         SendToICS(ics_prefix);
12612         SendToICS("draw\n");
12613         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12614     } else if (cmailMsgLoaded) {
12615         if (currentMove == cmailOldMove &&
12616             commentList[cmailOldMove] != NULL &&
12617             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12618                    "Black offers a draw" : "White offers a draw")) {
12619             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12620             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12621         } else if (currentMove == cmailOldMove + 1) {
12622             char *offer = WhiteOnMove(cmailOldMove) ?
12623               "White offers a draw" : "Black offers a draw";
12624             AppendComment(currentMove, offer, TRUE);
12625             DisplayComment(currentMove - 1, offer);
12626             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12627         } else {
12628             DisplayError(_("You must make your move before offering a draw"), 0);
12629             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12630         }
12631     } else if (first.offeredDraw) {
12632         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12633     } else {
12634         if (first.sendDrawOffers) {
12635             SendToProgram("draw\n", &first);
12636             userOfferedDraw = TRUE;
12637         }
12638     }
12639 }
12640
12641 void
12642 AdjournEvent()
12643 {
12644     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12645
12646     if (appData.icsActive) {
12647         SendToICS(ics_prefix);
12648         SendToICS("adjourn\n");
12649     } else {
12650         /* Currently GNU Chess doesn't offer or accept Adjourns */
12651     }
12652 }
12653
12654
12655 void
12656 AbortEvent()
12657 {
12658     /* Offer Abort or accept pending Abort offer from opponent */
12659
12660     if (appData.icsActive) {
12661         SendToICS(ics_prefix);
12662         SendToICS("abort\n");
12663     } else {
12664         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12665     }
12666 }
12667
12668 void
12669 ResignEvent()
12670 {
12671     /* Resign.  You can do this even if it's not your turn. */
12672
12673     if (appData.icsActive) {
12674         SendToICS(ics_prefix);
12675         SendToICS("resign\n");
12676     } else {
12677         switch (gameMode) {
12678           case MachinePlaysWhite:
12679             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12680             break;
12681           case MachinePlaysBlack:
12682             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12683             break;
12684           case EditGame:
12685             if (cmailMsgLoaded) {
12686                 TruncateGame();
12687                 if (WhiteOnMove(cmailOldMove)) {
12688                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12689                 } else {
12690                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12691                 }
12692                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12693             }
12694             break;
12695           default:
12696             break;
12697         }
12698     }
12699 }
12700
12701
12702 void
12703 StopObservingEvent()
12704 {
12705     /* Stop observing current games */
12706     SendToICS(ics_prefix);
12707     SendToICS("unobserve\n");
12708 }
12709
12710 void
12711 StopExaminingEvent()
12712 {
12713     /* Stop observing current game */
12714     SendToICS(ics_prefix);
12715     SendToICS("unexamine\n");
12716 }
12717
12718 void
12719 ForwardInner(target)
12720      int target;
12721 {
12722     int limit;
12723
12724     if (appData.debugMode)
12725         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12726                 target, currentMove, forwardMostMove);
12727
12728     if (gameMode == EditPosition)
12729       return;
12730
12731     if (gameMode == PlayFromGameFile && !pausing)
12732       PauseEvent();
12733
12734     if (gameMode == IcsExamining && pausing)
12735       limit = pauseExamForwardMostMove;
12736     else
12737       limit = forwardMostMove;
12738
12739     if (target > limit) target = limit;
12740
12741     if (target > 0 && moveList[target - 1][0]) {
12742         int fromX, fromY, toX, toY;
12743         toX = moveList[target - 1][2] - AAA;
12744         toY = moveList[target - 1][3] - ONE;
12745         if (moveList[target - 1][1] == '@') {
12746             if (appData.highlightLastMove) {
12747                 SetHighlights(-1, -1, toX, toY);
12748             }
12749         } else {
12750             fromX = moveList[target - 1][0] - AAA;
12751             fromY = moveList[target - 1][1] - ONE;
12752             if (target == currentMove + 1) {
12753                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12754             }
12755             if (appData.highlightLastMove) {
12756                 SetHighlights(fromX, fromY, toX, toY);
12757             }
12758         }
12759     }
12760     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12761         gameMode == Training || gameMode == PlayFromGameFile ||
12762         gameMode == AnalyzeFile) {
12763         while (currentMove < target) {
12764             SendMoveToProgram(currentMove++, &first);
12765         }
12766     } else {
12767         currentMove = target;
12768     }
12769
12770     if (gameMode == EditGame || gameMode == EndOfGame) {
12771         whiteTimeRemaining = timeRemaining[0][currentMove];
12772         blackTimeRemaining = timeRemaining[1][currentMove];
12773     }
12774     DisplayBothClocks();
12775     DisplayMove(currentMove - 1);
12776     DrawPosition(FALSE, boards[currentMove]);
12777     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12778     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12779         DisplayComment(currentMove - 1, commentList[currentMove]);
12780     }
12781 }
12782
12783
12784 void
12785 ForwardEvent()
12786 {
12787     if (gameMode == IcsExamining && !pausing) {
12788         SendToICS(ics_prefix);
12789         SendToICS("forward\n");
12790     } else {
12791         ForwardInner(currentMove + 1);
12792     }
12793 }
12794
12795 void
12796 ToEndEvent()
12797 {
12798     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12799         /* to optimze, we temporarily turn off analysis mode while we feed
12800          * the remaining moves to the engine. Otherwise we get analysis output
12801          * after each move.
12802          */
12803         if (first.analysisSupport) {
12804           SendToProgram("exit\nforce\n", &first);
12805           first.analyzing = FALSE;
12806         }
12807     }
12808
12809     if (gameMode == IcsExamining && !pausing) {
12810         SendToICS(ics_prefix);
12811         SendToICS("forward 999999\n");
12812     } else {
12813         ForwardInner(forwardMostMove);
12814     }
12815
12816     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12817         /* we have fed all the moves, so reactivate analysis mode */
12818         SendToProgram("analyze\n", &first);
12819         first.analyzing = TRUE;
12820         /*first.maybeThinking = TRUE;*/
12821         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12822     }
12823 }
12824
12825 void
12826 BackwardInner(target)
12827      int target;
12828 {
12829     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12830
12831     if (appData.debugMode)
12832         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12833                 target, currentMove, forwardMostMove);
12834
12835     if (gameMode == EditPosition) return;
12836     if (currentMove <= backwardMostMove) {
12837         ClearHighlights();
12838         DrawPosition(full_redraw, boards[currentMove]);
12839         return;
12840     }
12841     if (gameMode == PlayFromGameFile && !pausing)
12842       PauseEvent();
12843
12844     if (moveList[target][0]) {
12845         int fromX, fromY, toX, toY;
12846         toX = moveList[target][2] - AAA;
12847         toY = moveList[target][3] - ONE;
12848         if (moveList[target][1] == '@') {
12849             if (appData.highlightLastMove) {
12850                 SetHighlights(-1, -1, toX, toY);
12851             }
12852         } else {
12853             fromX = moveList[target][0] - AAA;
12854             fromY = moveList[target][1] - ONE;
12855             if (target == currentMove - 1) {
12856                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12857             }
12858             if (appData.highlightLastMove) {
12859                 SetHighlights(fromX, fromY, toX, toY);
12860             }
12861         }
12862     }
12863     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12864         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12865         while (currentMove > target) {
12866             SendToProgram("undo\n", &first);
12867             currentMove--;
12868         }
12869     } else {
12870         currentMove = target;
12871     }
12872
12873     if (gameMode == EditGame || gameMode == EndOfGame) {
12874         whiteTimeRemaining = timeRemaining[0][currentMove];
12875         blackTimeRemaining = timeRemaining[1][currentMove];
12876     }
12877     DisplayBothClocks();
12878     DisplayMove(currentMove - 1);
12879     DrawPosition(full_redraw, boards[currentMove]);
12880     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12881     // [HGM] PV info: routine tests if comment empty
12882     DisplayComment(currentMove - 1, commentList[currentMove]);
12883 }
12884
12885 void
12886 BackwardEvent()
12887 {
12888     if (gameMode == IcsExamining && !pausing) {
12889         SendToICS(ics_prefix);
12890         SendToICS("backward\n");
12891     } else {
12892         BackwardInner(currentMove - 1);
12893     }
12894 }
12895
12896 void
12897 ToStartEvent()
12898 {
12899     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12900         /* to optimize, we temporarily turn off analysis mode while we undo
12901          * all the moves. Otherwise we get analysis output after each undo.
12902          */
12903         if (first.analysisSupport) {
12904           SendToProgram("exit\nforce\n", &first);
12905           first.analyzing = FALSE;
12906         }
12907     }
12908
12909     if (gameMode == IcsExamining && !pausing) {
12910         SendToICS(ics_prefix);
12911         SendToICS("backward 999999\n");
12912     } else {
12913         BackwardInner(backwardMostMove);
12914     }
12915
12916     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12917         /* we have fed all the moves, so reactivate analysis mode */
12918         SendToProgram("analyze\n", &first);
12919         first.analyzing = TRUE;
12920         /*first.maybeThinking = TRUE;*/
12921         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12922     }
12923 }
12924
12925 void
12926 ToNrEvent(int to)
12927 {
12928   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12929   if (to >= forwardMostMove) to = forwardMostMove;
12930   if (to <= backwardMostMove) to = backwardMostMove;
12931   if (to < currentMove) {
12932     BackwardInner(to);
12933   } else {
12934     ForwardInner(to);
12935   }
12936 }
12937
12938 void
12939 RevertEvent(Boolean annotate)
12940 {
12941     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12942         return;
12943     }
12944     if (gameMode != IcsExamining) {
12945         DisplayError(_("You are not examining a game"), 0);
12946         return;
12947     }
12948     if (pausing) {
12949         DisplayError(_("You can't revert while pausing"), 0);
12950         return;
12951     }
12952     SendToICS(ics_prefix);
12953     SendToICS("revert\n");
12954 }
12955
12956 void
12957 RetractMoveEvent()
12958 {
12959     switch (gameMode) {
12960       case MachinePlaysWhite:
12961       case MachinePlaysBlack:
12962         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12963             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12964             return;
12965         }
12966         if (forwardMostMove < 2) return;
12967         currentMove = forwardMostMove = forwardMostMove - 2;
12968         whiteTimeRemaining = timeRemaining[0][currentMove];
12969         blackTimeRemaining = timeRemaining[1][currentMove];
12970         DisplayBothClocks();
12971         DisplayMove(currentMove - 1);
12972         ClearHighlights();/*!! could figure this out*/
12973         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12974         SendToProgram("remove\n", &first);
12975         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12976         break;
12977
12978       case BeginningOfGame:
12979       default:
12980         break;
12981
12982       case IcsPlayingWhite:
12983       case IcsPlayingBlack:
12984         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12985             SendToICS(ics_prefix);
12986             SendToICS("takeback 2\n");
12987         } else {
12988             SendToICS(ics_prefix);
12989             SendToICS("takeback 1\n");
12990         }
12991         break;
12992     }
12993 }
12994
12995 void
12996 MoveNowEvent()
12997 {
12998     ChessProgramState *cps;
12999
13000     switch (gameMode) {
13001       case MachinePlaysWhite:
13002         if (!WhiteOnMove(forwardMostMove)) {
13003             DisplayError(_("It is your turn"), 0);
13004             return;
13005         }
13006         cps = &first;
13007         break;
13008       case MachinePlaysBlack:
13009         if (WhiteOnMove(forwardMostMove)) {
13010             DisplayError(_("It is your turn"), 0);
13011             return;
13012         }
13013         cps = &first;
13014         break;
13015       case TwoMachinesPlay:
13016         if (WhiteOnMove(forwardMostMove) ==
13017             (first.twoMachinesColor[0] == 'w')) {
13018             cps = &first;
13019         } else {
13020             cps = &second;
13021         }
13022         break;
13023       case BeginningOfGame:
13024       default:
13025         return;
13026     }
13027     SendToProgram("?\n", cps);
13028 }
13029
13030 void
13031 TruncateGameEvent()
13032 {
13033     EditGameEvent();
13034     if (gameMode != EditGame) return;
13035     TruncateGame();
13036 }
13037
13038 void
13039 TruncateGame()
13040 {
13041     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13042     if (forwardMostMove > currentMove) {
13043         if (gameInfo.resultDetails != NULL) {
13044             free(gameInfo.resultDetails);
13045             gameInfo.resultDetails = NULL;
13046             gameInfo.result = GameUnfinished;
13047         }
13048         forwardMostMove = currentMove;
13049         HistorySet(parseList, backwardMostMove, forwardMostMove,
13050                    currentMove-1);
13051     }
13052 }
13053
13054 void
13055 HintEvent()
13056 {
13057     if (appData.noChessProgram) return;
13058     switch (gameMode) {
13059       case MachinePlaysWhite:
13060         if (WhiteOnMove(forwardMostMove)) {
13061             DisplayError(_("Wait until your turn"), 0);
13062             return;
13063         }
13064         break;
13065       case BeginningOfGame:
13066       case MachinePlaysBlack:
13067         if (!WhiteOnMove(forwardMostMove)) {
13068             DisplayError(_("Wait until your turn"), 0);
13069             return;
13070         }
13071         break;
13072       default:
13073         DisplayError(_("No hint available"), 0);
13074         return;
13075     }
13076     SendToProgram("hint\n", &first);
13077     hintRequested = TRUE;
13078 }
13079
13080 void
13081 BookEvent()
13082 {
13083     if (appData.noChessProgram) return;
13084     switch (gameMode) {
13085       case MachinePlaysWhite:
13086         if (WhiteOnMove(forwardMostMove)) {
13087             DisplayError(_("Wait until your turn"), 0);
13088             return;
13089         }
13090         break;
13091       case BeginningOfGame:
13092       case MachinePlaysBlack:
13093         if (!WhiteOnMove(forwardMostMove)) {
13094             DisplayError(_("Wait until your turn"), 0);
13095             return;
13096         }
13097         break;
13098       case EditPosition:
13099         EditPositionDone(TRUE);
13100         break;
13101       case TwoMachinesPlay:
13102         return;
13103       default:
13104         break;
13105     }
13106     SendToProgram("bk\n", &first);
13107     bookOutput[0] = NULLCHAR;
13108     bookRequested = TRUE;
13109 }
13110
13111 void
13112 AboutGameEvent()
13113 {
13114     char *tags = PGNTags(&gameInfo);
13115     TagsPopUp(tags, CmailMsg());
13116     free(tags);
13117 }
13118
13119 /* end button procedures */
13120
13121 void
13122 PrintPosition(fp, move)
13123      FILE *fp;
13124      int move;
13125 {
13126     int i, j;
13127
13128     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13129         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13130             char c = PieceToChar(boards[move][i][j]);
13131             fputc(c == 'x' ? '.' : c, fp);
13132             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13133         }
13134     }
13135     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13136       fprintf(fp, "white to play\n");
13137     else
13138       fprintf(fp, "black to play\n");
13139 }
13140
13141 void
13142 PrintOpponents(fp)
13143      FILE *fp;
13144 {
13145     if (gameInfo.white != NULL) {
13146         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13147     } else {
13148         fprintf(fp, "\n");
13149     }
13150 }
13151
13152 /* Find last component of program's own name, using some heuristics */
13153 void
13154 TidyProgramName(prog, host, buf)
13155      char *prog, *host, buf[MSG_SIZ];
13156 {
13157     char *p, *q;
13158     int local = (strcmp(host, "localhost") == 0);
13159     while (!local && (p = strchr(prog, ';')) != NULL) {
13160         p++;
13161         while (*p == ' ') p++;
13162         prog = p;
13163     }
13164     if (*prog == '"' || *prog == '\'') {
13165         q = strchr(prog + 1, *prog);
13166     } else {
13167         q = strchr(prog, ' ');
13168     }
13169     if (q == NULL) q = prog + strlen(prog);
13170     p = q;
13171     while (p >= prog && *p != '/' && *p != '\\') p--;
13172     p++;
13173     if(p == prog && *p == '"') p++;
13174     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13175     memcpy(buf, p, q - p);
13176     buf[q - p] = NULLCHAR;
13177     if (!local) {
13178         strcat(buf, "@");
13179         strcat(buf, host);
13180     }
13181 }
13182
13183 char *
13184 TimeControlTagValue()
13185 {
13186     char buf[MSG_SIZ];
13187     if (!appData.clockMode) {
13188       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13189     } else if (movesPerSession > 0) {
13190       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13191     } else if (timeIncrement == 0) {
13192       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13193     } else {
13194       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13195     }
13196     return StrSave(buf);
13197 }
13198
13199 void
13200 SetGameInfo()
13201 {
13202     /* This routine is used only for certain modes */
13203     VariantClass v = gameInfo.variant;
13204     ChessMove r = GameUnfinished;
13205     char *p = NULL;
13206
13207     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13208         r = gameInfo.result;
13209         p = gameInfo.resultDetails;
13210         gameInfo.resultDetails = NULL;
13211     }
13212     ClearGameInfo(&gameInfo);
13213     gameInfo.variant = v;
13214
13215     switch (gameMode) {
13216       case MachinePlaysWhite:
13217         gameInfo.event = StrSave( appData.pgnEventHeader );
13218         gameInfo.site = StrSave(HostName());
13219         gameInfo.date = PGNDate();
13220         gameInfo.round = StrSave("-");
13221         gameInfo.white = StrSave(first.tidy);
13222         gameInfo.black = StrSave(UserName());
13223         gameInfo.timeControl = TimeControlTagValue();
13224         break;
13225
13226       case MachinePlaysBlack:
13227         gameInfo.event = StrSave( appData.pgnEventHeader );
13228         gameInfo.site = StrSave(HostName());
13229         gameInfo.date = PGNDate();
13230         gameInfo.round = StrSave("-");
13231         gameInfo.white = StrSave(UserName());
13232         gameInfo.black = StrSave(first.tidy);
13233         gameInfo.timeControl = TimeControlTagValue();
13234         break;
13235
13236       case TwoMachinesPlay:
13237         gameInfo.event = StrSave( appData.pgnEventHeader );
13238         gameInfo.site = StrSave(HostName());
13239         gameInfo.date = PGNDate();
13240         if (matchGame > 0) {
13241             char buf[MSG_SIZ];
13242             snprintf(buf, MSG_SIZ, "%d", matchGame);
13243             gameInfo.round = StrSave(buf);
13244         } else {
13245             gameInfo.round = StrSave("-");
13246         }
13247         if (first.twoMachinesColor[0] == 'w') {
13248             gameInfo.white = StrSave(first.tidy);
13249             gameInfo.black = StrSave(second.tidy);
13250         } else {
13251             gameInfo.white = StrSave(second.tidy);
13252             gameInfo.black = StrSave(first.tidy);
13253         }
13254         gameInfo.timeControl = TimeControlTagValue();
13255         break;
13256
13257       case EditGame:
13258         gameInfo.event = StrSave("Edited game");
13259         gameInfo.site = StrSave(HostName());
13260         gameInfo.date = PGNDate();
13261         gameInfo.round = StrSave("-");
13262         gameInfo.white = StrSave("-");
13263         gameInfo.black = StrSave("-");
13264         gameInfo.result = r;
13265         gameInfo.resultDetails = p;
13266         break;
13267
13268       case EditPosition:
13269         gameInfo.event = StrSave("Edited position");
13270         gameInfo.site = StrSave(HostName());
13271         gameInfo.date = PGNDate();
13272         gameInfo.round = StrSave("-");
13273         gameInfo.white = StrSave("-");
13274         gameInfo.black = StrSave("-");
13275         break;
13276
13277       case IcsPlayingWhite:
13278       case IcsPlayingBlack:
13279       case IcsObserving:
13280       case IcsExamining:
13281         break;
13282
13283       case PlayFromGameFile:
13284         gameInfo.event = StrSave("Game from non-PGN file");
13285         gameInfo.site = StrSave(HostName());
13286         gameInfo.date = PGNDate();
13287         gameInfo.round = StrSave("-");
13288         gameInfo.white = StrSave("?");
13289         gameInfo.black = StrSave("?");
13290         break;
13291
13292       default:
13293         break;
13294     }
13295 }
13296
13297 void
13298 ReplaceComment(index, text)
13299      int index;
13300      char *text;
13301 {
13302     int len;
13303     char *p;
13304     float score;
13305
13306     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13307        pvInfoList[index-1].depth == len &&
13308        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13309        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13310     while (*text == '\n') text++;
13311     len = strlen(text);
13312     while (len > 0 && text[len - 1] == '\n') len--;
13313
13314     if (commentList[index] != NULL)
13315       free(commentList[index]);
13316
13317     if (len == 0) {
13318         commentList[index] = NULL;
13319         return;
13320     }
13321   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13322       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13323       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13324     commentList[index] = (char *) malloc(len + 2);
13325     strncpy(commentList[index], text, len);
13326     commentList[index][len] = '\n';
13327     commentList[index][len + 1] = NULLCHAR;
13328   } else {
13329     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13330     char *p;
13331     commentList[index] = (char *) malloc(len + 7);
13332     safeStrCpy(commentList[index], "{\n", 3);
13333     safeStrCpy(commentList[index]+2, text, len+1);
13334     commentList[index][len+2] = NULLCHAR;
13335     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13336     strcat(commentList[index], "\n}\n");
13337   }
13338 }
13339
13340 void
13341 CrushCRs(text)
13342      char *text;
13343 {
13344   char *p = text;
13345   char *q = text;
13346   char ch;
13347
13348   do {
13349     ch = *p++;
13350     if (ch == '\r') continue;
13351     *q++ = ch;
13352   } while (ch != '\0');
13353 }
13354
13355 void
13356 AppendComment(index, text, addBraces)
13357      int index;
13358      char *text;
13359      Boolean addBraces; // [HGM] braces: tells if we should add {}
13360 {
13361     int oldlen, len;
13362     char *old;
13363
13364 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13365     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13366
13367     CrushCRs(text);
13368     while (*text == '\n') text++;
13369     len = strlen(text);
13370     while (len > 0 && text[len - 1] == '\n') len--;
13371
13372     if (len == 0) return;
13373
13374     if (commentList[index] != NULL) {
13375         old = commentList[index];
13376         oldlen = strlen(old);
13377         while(commentList[index][oldlen-1] ==  '\n')
13378           commentList[index][--oldlen] = NULLCHAR;
13379         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13380         safeStrCpy(commentList[index], old, oldlen + len + 6);
13381         free(old);
13382         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13383         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13384           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13385           while (*text == '\n') { text++; len--; }
13386           commentList[index][--oldlen] = NULLCHAR;
13387       }
13388         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13389         else          strcat(commentList[index], "\n");
13390         strcat(commentList[index], text);
13391         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13392         else          strcat(commentList[index], "\n");
13393     } else {
13394         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13395         if(addBraces)
13396           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13397         else commentList[index][0] = NULLCHAR;
13398         strcat(commentList[index], text);
13399         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13400         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13401     }
13402 }
13403
13404 static char * FindStr( char * text, char * sub_text )
13405 {
13406     char * result = strstr( text, sub_text );
13407
13408     if( result != NULL ) {
13409         result += strlen( sub_text );
13410     }
13411
13412     return result;
13413 }
13414
13415 /* [AS] Try to extract PV info from PGN comment */
13416 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13417 char *GetInfoFromComment( int index, char * text )
13418 {
13419     char * sep = text, *p;
13420
13421     if( text != NULL && index > 0 ) {
13422         int score = 0;
13423         int depth = 0;
13424         int time = -1, sec = 0, deci;
13425         char * s_eval = FindStr( text, "[%eval " );
13426         char * s_emt = FindStr( text, "[%emt " );
13427
13428         if( s_eval != NULL || s_emt != NULL ) {
13429             /* New style */
13430             char delim;
13431
13432             if( s_eval != NULL ) {
13433                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13434                     return text;
13435                 }
13436
13437                 if( delim != ']' ) {
13438                     return text;
13439                 }
13440             }
13441
13442             if( s_emt != NULL ) {
13443             }
13444                 return text;
13445         }
13446         else {
13447             /* We expect something like: [+|-]nnn.nn/dd */
13448             int score_lo = 0;
13449
13450             if(*text != '{') return text; // [HGM] braces: must be normal comment
13451
13452             sep = strchr( text, '/' );
13453             if( sep == NULL || sep < (text+4) ) {
13454                 return text;
13455             }
13456
13457             p = text;
13458             if(p[1] == '(') { // comment starts with PV
13459                p = strchr(p, ')'); // locate end of PV
13460                if(p == NULL || sep < p+5) return text;
13461                // at this point we have something like "{(.*) +0.23/6 ..."
13462                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13463                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13464                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13465             }
13466             time = -1; sec = -1; deci = -1;
13467             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13468                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13469                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13470                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13471                 return text;
13472             }
13473
13474             if( score_lo < 0 || score_lo >= 100 ) {
13475                 return text;
13476             }
13477
13478             if(sec >= 0) time = 600*time + 10*sec; else
13479             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13480
13481             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13482
13483             /* [HGM] PV time: now locate end of PV info */
13484             while( *++sep >= '0' && *sep <= '9'); // strip depth
13485             if(time >= 0)
13486             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13487             if(sec >= 0)
13488             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13489             if(deci >= 0)
13490             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13491             while(*sep == ' ') sep++;
13492         }
13493
13494         if( depth <= 0 ) {
13495             return text;
13496         }
13497
13498         if( time < 0 ) {
13499             time = -1;
13500         }
13501
13502         pvInfoList[index-1].depth = depth;
13503         pvInfoList[index-1].score = score;
13504         pvInfoList[index-1].time  = 10*time; // centi-sec
13505         if(*sep == '}') *sep = 0; else *--sep = '{';
13506         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13507     }
13508     return sep;
13509 }
13510
13511 void
13512 SendToProgram(message, cps)
13513      char *message;
13514      ChessProgramState *cps;
13515 {
13516     int count, outCount, error;
13517     char buf[MSG_SIZ];
13518
13519     if (cps->pr == NULL) return;
13520     Attention(cps);
13521
13522     if (appData.debugMode) {
13523         TimeMark now;
13524         GetTimeMark(&now);
13525         fprintf(debugFP, "%ld >%-6s: %s",
13526                 SubtractTimeMarks(&now, &programStartTime),
13527                 cps->which, message);
13528     }
13529
13530     count = strlen(message);
13531     outCount = OutputToProcess(cps->pr, message, count, &error);
13532     if (outCount < count && !exiting
13533                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13534       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13535         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13536             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13537                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13538                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13539             } else {
13540                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13541             }
13542             gameInfo.resultDetails = StrSave(buf);
13543         }
13544         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13545     }
13546 }
13547
13548 void
13549 ReceiveFromProgram(isr, closure, message, count, error)
13550      InputSourceRef isr;
13551      VOIDSTAR closure;
13552      char *message;
13553      int count;
13554      int error;
13555 {
13556     char *end_str;
13557     char buf[MSG_SIZ];
13558     ChessProgramState *cps = (ChessProgramState *)closure;
13559
13560     if (isr != cps->isr) return; /* Killed intentionally */
13561     if (count <= 0) {
13562         if (count == 0) {
13563             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13564                     cps->which, cps->program);
13565         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13566                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13567                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13568                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13569                 } else {
13570                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13571                 }
13572                 gameInfo.resultDetails = StrSave(buf);
13573             }
13574             RemoveInputSource(cps->isr);
13575             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13576         } else {
13577             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13578                     cps->which, cps->program);
13579             RemoveInputSource(cps->isr);
13580
13581             /* [AS] Program is misbehaving badly... kill it */
13582             if( count == -2 ) {
13583                 DestroyChildProcess( cps->pr, 9 );
13584                 cps->pr = NoProc;
13585             }
13586
13587             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13588         }
13589         return;
13590     }
13591
13592     if ((end_str = strchr(message, '\r')) != NULL)
13593       *end_str = NULLCHAR;
13594     if ((end_str = strchr(message, '\n')) != NULL)
13595       *end_str = NULLCHAR;
13596
13597     if (appData.debugMode) {
13598         TimeMark now; int print = 1;
13599         char *quote = ""; char c; int i;
13600
13601         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13602                 char start = message[0];
13603                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13604                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13605                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13606                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13607                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13608                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13609                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13610                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13611                    sscanf(message, "hint: %c", &c)!=1 && 
13612                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13613                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13614                     print = (appData.engineComments >= 2);
13615                 }
13616                 message[0] = start; // restore original message
13617         }
13618         if(print) {
13619                 GetTimeMark(&now);
13620                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13621                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13622                         quote,
13623                         message);
13624         }
13625     }
13626
13627     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13628     if (appData.icsEngineAnalyze) {
13629         if (strstr(message, "whisper") != NULL ||
13630              strstr(message, "kibitz") != NULL ||
13631             strstr(message, "tellics") != NULL) return;
13632     }
13633
13634     HandleMachineMove(message, cps);
13635 }
13636
13637
13638 void
13639 SendTimeControl(cps, mps, tc, inc, sd, st)
13640      ChessProgramState *cps;
13641      int mps, inc, sd, st;
13642      long tc;
13643 {
13644     char buf[MSG_SIZ];
13645     int seconds;
13646
13647     if( timeControl_2 > 0 ) {
13648         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13649             tc = timeControl_2;
13650         }
13651     }
13652     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13653     inc /= cps->timeOdds;
13654     st  /= cps->timeOdds;
13655
13656     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13657
13658     if (st > 0) {
13659       /* Set exact time per move, normally using st command */
13660       if (cps->stKludge) {
13661         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13662         seconds = st % 60;
13663         if (seconds == 0) {
13664           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13665         } else {
13666           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13667         }
13668       } else {
13669         snprintf(buf, MSG_SIZ, "st %d\n", st);
13670       }
13671     } else {
13672       /* Set conventional or incremental time control, using level command */
13673       if (seconds == 0) {
13674         /* Note old gnuchess bug -- minutes:seconds used to not work.
13675            Fixed in later versions, but still avoid :seconds
13676            when seconds is 0. */
13677         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13678       } else {
13679         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13680                  seconds, inc/1000.);
13681       }
13682     }
13683     SendToProgram(buf, cps);
13684
13685     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13686     /* Orthogonally, limit search to given depth */
13687     if (sd > 0) {
13688       if (cps->sdKludge) {
13689         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13690       } else {
13691         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13692       }
13693       SendToProgram(buf, cps);
13694     }
13695
13696     if(cps->nps > 0) { /* [HGM] nps */
13697         if(cps->supportsNPS == FALSE)
13698           cps->nps = -1; // don't use if engine explicitly says not supported!
13699         else {
13700           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13701           SendToProgram(buf, cps);
13702         }
13703     }
13704 }
13705
13706 ChessProgramState *WhitePlayer()
13707 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13708 {
13709     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13710        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13711         return &second;
13712     return &first;
13713 }
13714
13715 void
13716 SendTimeRemaining(cps, machineWhite)
13717      ChessProgramState *cps;
13718      int /*boolean*/ machineWhite;
13719 {
13720     char message[MSG_SIZ];
13721     long time, otime;
13722
13723     /* Note: this routine must be called when the clocks are stopped
13724        or when they have *just* been set or switched; otherwise
13725        it will be off by the time since the current tick started.
13726     */
13727     if (machineWhite) {
13728         time = whiteTimeRemaining / 10;
13729         otime = blackTimeRemaining / 10;
13730     } else {
13731         time = blackTimeRemaining / 10;
13732         otime = whiteTimeRemaining / 10;
13733     }
13734     /* [HGM] translate opponent's time by time-odds factor */
13735     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13736     if (appData.debugMode) {
13737         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13738     }
13739
13740     if (time <= 0) time = 1;
13741     if (otime <= 0) otime = 1;
13742
13743     snprintf(message, MSG_SIZ, "time %ld\n", time);
13744     SendToProgram(message, cps);
13745
13746     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13747     SendToProgram(message, cps);
13748 }
13749
13750 int
13751 BoolFeature(p, name, loc, cps)
13752      char **p;
13753      char *name;
13754      int *loc;
13755      ChessProgramState *cps;
13756 {
13757   char buf[MSG_SIZ];
13758   int len = strlen(name);
13759   int val;
13760
13761   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13762     (*p) += len + 1;
13763     sscanf(*p, "%d", &val);
13764     *loc = (val != 0);
13765     while (**p && **p != ' ')
13766       (*p)++;
13767     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13768     SendToProgram(buf, cps);
13769     return TRUE;
13770   }
13771   return FALSE;
13772 }
13773
13774 int
13775 IntFeature(p, name, loc, cps)
13776      char **p;
13777      char *name;
13778      int *loc;
13779      ChessProgramState *cps;
13780 {
13781   char buf[MSG_SIZ];
13782   int len = strlen(name);
13783   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13784     (*p) += len + 1;
13785     sscanf(*p, "%d", loc);
13786     while (**p && **p != ' ') (*p)++;
13787     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13788     SendToProgram(buf, cps);
13789     return TRUE;
13790   }
13791   return FALSE;
13792 }
13793
13794 int
13795 StringFeature(p, name, loc, cps)
13796      char **p;
13797      char *name;
13798      char loc[];
13799      ChessProgramState *cps;
13800 {
13801   char buf[MSG_SIZ];
13802   int len = strlen(name);
13803   if (strncmp((*p), name, len) == 0
13804       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13805     (*p) += len + 2;
13806     sscanf(*p, "%[^\"]", loc);
13807     while (**p && **p != '\"') (*p)++;
13808     if (**p == '\"') (*p)++;
13809     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13810     SendToProgram(buf, cps);
13811     return TRUE;
13812   }
13813   return FALSE;
13814 }
13815
13816 int
13817 ParseOption(Option *opt, ChessProgramState *cps)
13818 // [HGM] options: process the string that defines an engine option, and determine
13819 // name, type, default value, and allowed value range
13820 {
13821         char *p, *q, buf[MSG_SIZ];
13822         int n, min = (-1)<<31, max = 1<<31, def;
13823
13824         if(p = strstr(opt->name, " -spin ")) {
13825             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13826             if(max < min) max = min; // enforce consistency
13827             if(def < min) def = min;
13828             if(def > max) def = max;
13829             opt->value = def;
13830             opt->min = min;
13831             opt->max = max;
13832             opt->type = Spin;
13833         } else if((p = strstr(opt->name, " -slider "))) {
13834             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13835             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13836             if(max < min) max = min; // enforce consistency
13837             if(def < min) def = min;
13838             if(def > max) def = max;
13839             opt->value = def;
13840             opt->min = min;
13841             opt->max = max;
13842             opt->type = Spin; // Slider;
13843         } else if((p = strstr(opt->name, " -string "))) {
13844             opt->textValue = p+9;
13845             opt->type = TextBox;
13846         } else if((p = strstr(opt->name, " -file "))) {
13847             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13848             opt->textValue = p+7;
13849             opt->type = TextBox; // FileName;
13850         } else if((p = strstr(opt->name, " -path "))) {
13851             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13852             opt->textValue = p+7;
13853             opt->type = TextBox; // PathName;
13854         } else if(p = strstr(opt->name, " -check ")) {
13855             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13856             opt->value = (def != 0);
13857             opt->type = CheckBox;
13858         } else if(p = strstr(opt->name, " -combo ")) {
13859             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13860             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13861             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13862             opt->value = n = 0;
13863             while(q = StrStr(q, " /// ")) {
13864                 n++; *q = 0;    // count choices, and null-terminate each of them
13865                 q += 5;
13866                 if(*q == '*') { // remember default, which is marked with * prefix
13867                     q++;
13868                     opt->value = n;
13869                 }
13870                 cps->comboList[cps->comboCnt++] = q;
13871             }
13872             cps->comboList[cps->comboCnt++] = NULL;
13873             opt->max = n + 1;
13874             opt->type = ComboBox;
13875         } else if(p = strstr(opt->name, " -button")) {
13876             opt->type = Button;
13877         } else if(p = strstr(opt->name, " -save")) {
13878             opt->type = SaveButton;
13879         } else return FALSE;
13880         *p = 0; // terminate option name
13881         // now look if the command-line options define a setting for this engine option.
13882         if(cps->optionSettings && cps->optionSettings[0])
13883             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13884         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13885           snprintf(buf, MSG_SIZ, "option %s", p);
13886                 if(p = strstr(buf, ",")) *p = 0;
13887                 if(q = strchr(buf, '=')) switch(opt->type) {
13888                     case ComboBox:
13889                         for(n=0; n<opt->max; n++)
13890                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13891                         break;
13892                     case TextBox:
13893                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13894                         break;
13895                     case Spin:
13896                     case CheckBox:
13897                         opt->value = atoi(q+1);
13898                     default:
13899                         break;
13900                 }
13901                 strcat(buf, "\n");
13902                 SendToProgram(buf, cps);
13903         }
13904         return TRUE;
13905 }
13906
13907 void
13908 FeatureDone(cps, val)
13909      ChessProgramState* cps;
13910      int val;
13911 {
13912   DelayedEventCallback cb = GetDelayedEvent();
13913   if ((cb == InitBackEnd3 && cps == &first) ||
13914       (cb == SettingsMenuIfReady && cps == &second) ||
13915       (cb == TwoMachinesEventIfReady && cps == &second)) {
13916     CancelDelayedEvent();
13917     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13918   }
13919   cps->initDone = val;
13920 }
13921
13922 /* Parse feature command from engine */
13923 void
13924 ParseFeatures(args, cps)
13925      char* args;
13926      ChessProgramState *cps;
13927 {
13928   char *p = args;
13929   char *q;
13930   int val;
13931   char buf[MSG_SIZ];
13932
13933   for (;;) {
13934     while (*p == ' ') p++;
13935     if (*p == NULLCHAR) return;
13936
13937     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13938     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13939     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13940     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13941     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13942     if (BoolFeature(&p, "reuse", &val, cps)) {
13943       /* Engine can disable reuse, but can't enable it if user said no */
13944       if (!val) cps->reuse = FALSE;
13945       continue;
13946     }
13947     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13948     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13949       if (gameMode == TwoMachinesPlay) {
13950         DisplayTwoMachinesTitle();
13951       } else {
13952         DisplayTitle("");
13953       }
13954       continue;
13955     }
13956     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13957     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13958     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13959     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13960     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13961     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13962     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13963     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13964     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13965     if (IntFeature(&p, "done", &val, cps)) {
13966       FeatureDone(cps, val);
13967       continue;
13968     }
13969     /* Added by Tord: */
13970     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13971     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13972     /* End of additions by Tord */
13973
13974     /* [HGM] added features: */
13975     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13976     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13977     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13978     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13979     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13980     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13981     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13982         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13983           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13984             SendToProgram(buf, cps);
13985             continue;
13986         }
13987         if(cps->nrOptions >= MAX_OPTIONS) {
13988             cps->nrOptions--;
13989             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13990             DisplayError(buf, 0);
13991         }
13992         continue;
13993     }
13994     /* End of additions by HGM */
13995
13996     /* unknown feature: complain and skip */
13997     q = p;
13998     while (*q && *q != '=') q++;
13999     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14000     SendToProgram(buf, cps);
14001     p = q;
14002     if (*p == '=') {
14003       p++;
14004       if (*p == '\"') {
14005         p++;
14006         while (*p && *p != '\"') p++;
14007         if (*p == '\"') p++;
14008       } else {
14009         while (*p && *p != ' ') p++;
14010       }
14011     }
14012   }
14013
14014 }
14015
14016 void
14017 PeriodicUpdatesEvent(newState)
14018      int newState;
14019 {
14020     if (newState == appData.periodicUpdates)
14021       return;
14022
14023     appData.periodicUpdates=newState;
14024
14025     /* Display type changes, so update it now */
14026 //    DisplayAnalysis();
14027
14028     /* Get the ball rolling again... */
14029     if (newState) {
14030         AnalysisPeriodicEvent(1);
14031         StartAnalysisClock();
14032     }
14033 }
14034
14035 void
14036 PonderNextMoveEvent(newState)
14037      int newState;
14038 {
14039     if (newState == appData.ponderNextMove) return;
14040     if (gameMode == EditPosition) EditPositionDone(TRUE);
14041     if (newState) {
14042         SendToProgram("hard\n", &first);
14043         if (gameMode == TwoMachinesPlay) {
14044             SendToProgram("hard\n", &second);
14045         }
14046     } else {
14047         SendToProgram("easy\n", &first);
14048         thinkOutput[0] = NULLCHAR;
14049         if (gameMode == TwoMachinesPlay) {
14050             SendToProgram("easy\n", &second);
14051         }
14052     }
14053     appData.ponderNextMove = newState;
14054 }
14055
14056 void
14057 NewSettingEvent(option, feature, command, value)
14058      char *command;
14059      int option, value, *feature;
14060 {
14061     char buf[MSG_SIZ];
14062
14063     if (gameMode == EditPosition) EditPositionDone(TRUE);
14064     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14065     if(feature == NULL || *feature) SendToProgram(buf, &first);
14066     if (gameMode == TwoMachinesPlay) {
14067         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14068     }
14069 }
14070
14071 void
14072 ShowThinkingEvent()
14073 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14074 {
14075     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14076     int newState = appData.showThinking
14077         // [HGM] thinking: other features now need thinking output as well
14078         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14079
14080     if (oldState == newState) return;
14081     oldState = newState;
14082     if (gameMode == EditPosition) EditPositionDone(TRUE);
14083     if (oldState) {
14084         SendToProgram("post\n", &first);
14085         if (gameMode == TwoMachinesPlay) {
14086             SendToProgram("post\n", &second);
14087         }
14088     } else {
14089         SendToProgram("nopost\n", &first);
14090         thinkOutput[0] = NULLCHAR;
14091         if (gameMode == TwoMachinesPlay) {
14092             SendToProgram("nopost\n", &second);
14093         }
14094     }
14095 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14096 }
14097
14098 void
14099 AskQuestionEvent(title, question, replyPrefix, which)
14100      char *title; char *question; char *replyPrefix; char *which;
14101 {
14102   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14103   if (pr == NoProc) return;
14104   AskQuestion(title, question, replyPrefix, pr);
14105 }
14106
14107 void
14108 DisplayMove(moveNumber)
14109      int moveNumber;
14110 {
14111     char message[MSG_SIZ];
14112     char res[MSG_SIZ];
14113     char cpThinkOutput[MSG_SIZ];
14114
14115     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14116
14117     if (moveNumber == forwardMostMove - 1 ||
14118         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14119
14120         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14121
14122         if (strchr(cpThinkOutput, '\n')) {
14123             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14124         }
14125     } else {
14126         *cpThinkOutput = NULLCHAR;
14127     }
14128
14129     /* [AS] Hide thinking from human user */
14130     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14131         *cpThinkOutput = NULLCHAR;
14132         if( thinkOutput[0] != NULLCHAR ) {
14133             int i;
14134
14135             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14136                 cpThinkOutput[i] = '.';
14137             }
14138             cpThinkOutput[i] = NULLCHAR;
14139             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14140         }
14141     }
14142
14143     if (moveNumber == forwardMostMove - 1 &&
14144         gameInfo.resultDetails != NULL) {
14145         if (gameInfo.resultDetails[0] == NULLCHAR) {
14146           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14147         } else {
14148           snprintf(res, MSG_SIZ, " {%s} %s",
14149                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14150         }
14151     } else {
14152         res[0] = NULLCHAR;
14153     }
14154
14155     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14156         DisplayMessage(res, cpThinkOutput);
14157     } else {
14158       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14159                 WhiteOnMove(moveNumber) ? " " : ".. ",
14160                 parseList[moveNumber], res);
14161         DisplayMessage(message, cpThinkOutput);
14162     }
14163 }
14164
14165 void
14166 DisplayComment(moveNumber, text)
14167      int moveNumber;
14168      char *text;
14169 {
14170     char title[MSG_SIZ];
14171     char buf[8000]; // comment can be long!
14172     int score, depth;
14173
14174     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14175       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14176     } else {
14177       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14178               WhiteOnMove(moveNumber) ? " " : ".. ",
14179               parseList[moveNumber]);
14180     }
14181     // [HGM] PV info: display PV info together with (or as) comment
14182     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14183       if(text == NULL) text = "";
14184       score = pvInfoList[moveNumber].score;
14185       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14186               depth, (pvInfoList[moveNumber].time+50)/100, text);
14187       text = buf;
14188     }
14189     if (text != NULL && (appData.autoDisplayComment || commentUp))
14190         CommentPopUp(title, text);
14191 }
14192
14193 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14194  * might be busy thinking or pondering.  It can be omitted if your
14195  * gnuchess is configured to stop thinking immediately on any user
14196  * input.  However, that gnuchess feature depends on the FIONREAD
14197  * ioctl, which does not work properly on some flavors of Unix.
14198  */
14199 void
14200 Attention(cps)
14201      ChessProgramState *cps;
14202 {
14203 #if ATTENTION
14204     if (!cps->useSigint) return;
14205     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14206     switch (gameMode) {
14207       case MachinePlaysWhite:
14208       case MachinePlaysBlack:
14209       case TwoMachinesPlay:
14210       case IcsPlayingWhite:
14211       case IcsPlayingBlack:
14212       case AnalyzeMode:
14213       case AnalyzeFile:
14214         /* Skip if we know it isn't thinking */
14215         if (!cps->maybeThinking) return;
14216         if (appData.debugMode)
14217           fprintf(debugFP, "Interrupting %s\n", cps->which);
14218         InterruptChildProcess(cps->pr);
14219         cps->maybeThinking = FALSE;
14220         break;
14221       default:
14222         break;
14223     }
14224 #endif /*ATTENTION*/
14225 }
14226
14227 int
14228 CheckFlags()
14229 {
14230     if (whiteTimeRemaining <= 0) {
14231         if (!whiteFlag) {
14232             whiteFlag = TRUE;
14233             if (appData.icsActive) {
14234                 if (appData.autoCallFlag &&
14235                     gameMode == IcsPlayingBlack && !blackFlag) {
14236                   SendToICS(ics_prefix);
14237                   SendToICS("flag\n");
14238                 }
14239             } else {
14240                 if (blackFlag) {
14241                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14242                 } else {
14243                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14244                     if (appData.autoCallFlag) {
14245                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14246                         return TRUE;
14247                     }
14248                 }
14249             }
14250         }
14251     }
14252     if (blackTimeRemaining <= 0) {
14253         if (!blackFlag) {
14254             blackFlag = TRUE;
14255             if (appData.icsActive) {
14256                 if (appData.autoCallFlag &&
14257                     gameMode == IcsPlayingWhite && !whiteFlag) {
14258                   SendToICS(ics_prefix);
14259                   SendToICS("flag\n");
14260                 }
14261             } else {
14262                 if (whiteFlag) {
14263                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14264                 } else {
14265                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14266                     if (appData.autoCallFlag) {
14267                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14268                         return TRUE;
14269                     }
14270                 }
14271             }
14272         }
14273     }
14274     return FALSE;
14275 }
14276
14277 void
14278 CheckTimeControl()
14279 {
14280     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14281         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14282
14283     /*
14284      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14285      */
14286     if ( !WhiteOnMove(forwardMostMove) ) {
14287         /* White made time control */
14288         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14289         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14290         /* [HGM] time odds: correct new time quota for time odds! */
14291                                             / WhitePlayer()->timeOdds;
14292         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14293     } else {
14294         lastBlack -= blackTimeRemaining;
14295         /* Black made time control */
14296         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14297                                             / WhitePlayer()->other->timeOdds;
14298         lastWhite = whiteTimeRemaining;
14299     }
14300 }
14301
14302 void
14303 DisplayBothClocks()
14304 {
14305     int wom = gameMode == EditPosition ?
14306       !blackPlaysFirst : WhiteOnMove(currentMove);
14307     DisplayWhiteClock(whiteTimeRemaining, wom);
14308     DisplayBlackClock(blackTimeRemaining, !wom);
14309 }
14310
14311
14312 /* Timekeeping seems to be a portability nightmare.  I think everyone
14313    has ftime(), but I'm really not sure, so I'm including some ifdefs
14314    to use other calls if you don't.  Clocks will be less accurate if
14315    you have neither ftime nor gettimeofday.
14316 */
14317
14318 /* VS 2008 requires the #include outside of the function */
14319 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14320 #include <sys/timeb.h>
14321 #endif
14322
14323 /* Get the current time as a TimeMark */
14324 void
14325 GetTimeMark(tm)
14326      TimeMark *tm;
14327 {
14328 #if HAVE_GETTIMEOFDAY
14329
14330     struct timeval timeVal;
14331     struct timezone timeZone;
14332
14333     gettimeofday(&timeVal, &timeZone);
14334     tm->sec = (long) timeVal.tv_sec;
14335     tm->ms = (int) (timeVal.tv_usec / 1000L);
14336
14337 #else /*!HAVE_GETTIMEOFDAY*/
14338 #if HAVE_FTIME
14339
14340 // include <sys/timeb.h> / moved to just above start of function
14341     struct timeb timeB;
14342
14343     ftime(&timeB);
14344     tm->sec = (long) timeB.time;
14345     tm->ms = (int) timeB.millitm;
14346
14347 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14348     tm->sec = (long) time(NULL);
14349     tm->ms = 0;
14350 #endif
14351 #endif
14352 }
14353
14354 /* Return the difference in milliseconds between two
14355    time marks.  We assume the difference will fit in a long!
14356 */
14357 long
14358 SubtractTimeMarks(tm2, tm1)
14359      TimeMark *tm2, *tm1;
14360 {
14361     return 1000L*(tm2->sec - tm1->sec) +
14362            (long) (tm2->ms - tm1->ms);
14363 }
14364
14365
14366 /*
14367  * Code to manage the game clocks.
14368  *
14369  * In tournament play, black starts the clock and then white makes a move.
14370  * We give the human user a slight advantage if he is playing white---the
14371  * clocks don't run until he makes his first move, so it takes zero time.
14372  * Also, we don't account for network lag, so we could get out of sync
14373  * with GNU Chess's clock -- but then, referees are always right.
14374  */
14375
14376 static TimeMark tickStartTM;
14377 static long intendedTickLength;
14378
14379 long
14380 NextTickLength(timeRemaining)
14381      long timeRemaining;
14382 {
14383     long nominalTickLength, nextTickLength;
14384
14385     if (timeRemaining > 0L && timeRemaining <= 10000L)
14386       nominalTickLength = 100L;
14387     else
14388       nominalTickLength = 1000L;
14389     nextTickLength = timeRemaining % nominalTickLength;
14390     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14391
14392     return nextTickLength;
14393 }
14394
14395 /* Adjust clock one minute up or down */
14396 void
14397 AdjustClock(Boolean which, int dir)
14398 {
14399     if(which) blackTimeRemaining += 60000*dir;
14400     else      whiteTimeRemaining += 60000*dir;
14401     DisplayBothClocks();
14402 }
14403
14404 /* Stop clocks and reset to a fresh time control */
14405 void
14406 ResetClocks()
14407 {
14408     (void) StopClockTimer();
14409     if (appData.icsActive) {
14410         whiteTimeRemaining = blackTimeRemaining = 0;
14411     } else if (searchTime) {
14412         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14413         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14414     } else { /* [HGM] correct new time quote for time odds */
14415         whiteTC = blackTC = fullTimeControlString;
14416         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14417         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14418     }
14419     if (whiteFlag || blackFlag) {
14420         DisplayTitle("");
14421         whiteFlag = blackFlag = FALSE;
14422     }
14423     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14424     DisplayBothClocks();
14425 }
14426
14427 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14428
14429 /* Decrement running clock by amount of time that has passed */
14430 void
14431 DecrementClocks()
14432 {
14433     long timeRemaining;
14434     long lastTickLength, fudge;
14435     TimeMark now;
14436
14437     if (!appData.clockMode) return;
14438     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14439
14440     GetTimeMark(&now);
14441
14442     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14443
14444     /* Fudge if we woke up a little too soon */
14445     fudge = intendedTickLength - lastTickLength;
14446     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14447
14448     if (WhiteOnMove(forwardMostMove)) {
14449         if(whiteNPS >= 0) lastTickLength = 0;
14450         timeRemaining = whiteTimeRemaining -= lastTickLength;
14451         if(timeRemaining < 0 && !appData.icsActive) {
14452             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14453             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14454                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14455                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14456             }
14457         }
14458         DisplayWhiteClock(whiteTimeRemaining - fudge,
14459                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14460     } else {
14461         if(blackNPS >= 0) lastTickLength = 0;
14462         timeRemaining = blackTimeRemaining -= lastTickLength;
14463         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14464             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14465             if(suddenDeath) {
14466                 blackStartMove = forwardMostMove;
14467                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14468             }
14469         }
14470         DisplayBlackClock(blackTimeRemaining - fudge,
14471                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14472     }
14473     if (CheckFlags()) return;
14474
14475     tickStartTM = now;
14476     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14477     StartClockTimer(intendedTickLength);
14478
14479     /* if the time remaining has fallen below the alarm threshold, sound the
14480      * alarm. if the alarm has sounded and (due to a takeback or time control
14481      * with increment) the time remaining has increased to a level above the
14482      * threshold, reset the alarm so it can sound again.
14483      */
14484
14485     if (appData.icsActive && appData.icsAlarm) {
14486
14487         /* make sure we are dealing with the user's clock */
14488         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14489                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14490            )) return;
14491
14492         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14493             alarmSounded = FALSE;
14494         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14495             PlayAlarmSound();
14496             alarmSounded = TRUE;
14497         }
14498     }
14499 }
14500
14501
14502 /* A player has just moved, so stop the previously running
14503    clock and (if in clock mode) start the other one.
14504    We redisplay both clocks in case we're in ICS mode, because
14505    ICS gives us an update to both clocks after every move.
14506    Note that this routine is called *after* forwardMostMove
14507    is updated, so the last fractional tick must be subtracted
14508    from the color that is *not* on move now.
14509 */
14510 void
14511 SwitchClocks(int newMoveNr)
14512 {
14513     long lastTickLength;
14514     TimeMark now;
14515     int flagged = FALSE;
14516
14517     GetTimeMark(&now);
14518
14519     if (StopClockTimer() && appData.clockMode) {
14520         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14521         if (!WhiteOnMove(forwardMostMove)) {
14522             if(blackNPS >= 0) lastTickLength = 0;
14523             blackTimeRemaining -= lastTickLength;
14524            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14525 //         if(pvInfoList[forwardMostMove-1].time == -1)
14526                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14527                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14528         } else {
14529            if(whiteNPS >= 0) lastTickLength = 0;
14530            whiteTimeRemaining -= lastTickLength;
14531            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14532 //         if(pvInfoList[forwardMostMove-1].time == -1)
14533                  pvInfoList[forwardMostMove-1].time =
14534                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14535         }
14536         flagged = CheckFlags();
14537     }
14538     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14539     CheckTimeControl();
14540
14541     if (flagged || !appData.clockMode) return;
14542
14543     switch (gameMode) {
14544       case MachinePlaysBlack:
14545       case MachinePlaysWhite:
14546       case BeginningOfGame:
14547         if (pausing) return;
14548         break;
14549
14550       case EditGame:
14551       case PlayFromGameFile:
14552       case IcsExamining:
14553         return;
14554
14555       default:
14556         break;
14557     }
14558
14559     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14560         if(WhiteOnMove(forwardMostMove))
14561              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14562         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14563     }
14564
14565     tickStartTM = now;
14566     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14567       whiteTimeRemaining : blackTimeRemaining);
14568     StartClockTimer(intendedTickLength);
14569 }
14570
14571
14572 /* Stop both clocks */
14573 void
14574 StopClocks()
14575 {
14576     long lastTickLength;
14577     TimeMark now;
14578
14579     if (!StopClockTimer()) return;
14580     if (!appData.clockMode) return;
14581
14582     GetTimeMark(&now);
14583
14584     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14585     if (WhiteOnMove(forwardMostMove)) {
14586         if(whiteNPS >= 0) lastTickLength = 0;
14587         whiteTimeRemaining -= lastTickLength;
14588         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14589     } else {
14590         if(blackNPS >= 0) lastTickLength = 0;
14591         blackTimeRemaining -= lastTickLength;
14592         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14593     }
14594     CheckFlags();
14595 }
14596
14597 /* Start clock of player on move.  Time may have been reset, so
14598    if clock is already running, stop and restart it. */
14599 void
14600 StartClocks()
14601 {
14602     (void) StopClockTimer(); /* in case it was running already */
14603     DisplayBothClocks();
14604     if (CheckFlags()) return;
14605
14606     if (!appData.clockMode) return;
14607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14608
14609     GetTimeMark(&tickStartTM);
14610     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14611       whiteTimeRemaining : blackTimeRemaining);
14612
14613    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14614     whiteNPS = blackNPS = -1;
14615     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14616        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14617         whiteNPS = first.nps;
14618     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14619        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14620         blackNPS = first.nps;
14621     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14622         whiteNPS = second.nps;
14623     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14624         blackNPS = second.nps;
14625     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14626
14627     StartClockTimer(intendedTickLength);
14628 }
14629
14630 char *
14631 TimeString(ms)
14632      long ms;
14633 {
14634     long second, minute, hour, day;
14635     char *sign = "";
14636     static char buf[32];
14637
14638     if (ms > 0 && ms <= 9900) {
14639       /* convert milliseconds to tenths, rounding up */
14640       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14641
14642       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14643       return buf;
14644     }
14645
14646     /* convert milliseconds to seconds, rounding up */
14647     /* use floating point to avoid strangeness of integer division
14648        with negative dividends on many machines */
14649     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14650
14651     if (second < 0) {
14652         sign = "-";
14653         second = -second;
14654     }
14655
14656     day = second / (60 * 60 * 24);
14657     second = second % (60 * 60 * 24);
14658     hour = second / (60 * 60);
14659     second = second % (60 * 60);
14660     minute = second / 60;
14661     second = second % 60;
14662
14663     if (day > 0)
14664       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14665               sign, day, hour, minute, second);
14666     else if (hour > 0)
14667       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14668     else
14669       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14670
14671     return buf;
14672 }
14673
14674
14675 /*
14676  * This is necessary because some C libraries aren't ANSI C compliant yet.
14677  */
14678 char *
14679 StrStr(string, match)
14680      char *string, *match;
14681 {
14682     int i, length;
14683
14684     length = strlen(match);
14685
14686     for (i = strlen(string) - length; i >= 0; i--, string++)
14687       if (!strncmp(match, string, length))
14688         return string;
14689
14690     return NULL;
14691 }
14692
14693 char *
14694 StrCaseStr(string, match)
14695      char *string, *match;
14696 {
14697     int i, j, length;
14698
14699     length = strlen(match);
14700
14701     for (i = strlen(string) - length; i >= 0; i--, string++) {
14702         for (j = 0; j < length; j++) {
14703             if (ToLower(match[j]) != ToLower(string[j]))
14704               break;
14705         }
14706         if (j == length) return string;
14707     }
14708
14709     return NULL;
14710 }
14711
14712 #ifndef _amigados
14713 int
14714 StrCaseCmp(s1, s2)
14715      char *s1, *s2;
14716 {
14717     char c1, c2;
14718
14719     for (;;) {
14720         c1 = ToLower(*s1++);
14721         c2 = ToLower(*s2++);
14722         if (c1 > c2) return 1;
14723         if (c1 < c2) return -1;
14724         if (c1 == NULLCHAR) return 0;
14725     }
14726 }
14727
14728
14729 int
14730 ToLower(c)
14731      int c;
14732 {
14733     return isupper(c) ? tolower(c) : c;
14734 }
14735
14736
14737 int
14738 ToUpper(c)
14739      int c;
14740 {
14741     return islower(c) ? toupper(c) : c;
14742 }
14743 #endif /* !_amigados    */
14744
14745 char *
14746 StrSave(s)
14747      char *s;
14748 {
14749   char *ret;
14750
14751   if ((ret = (char *) malloc(strlen(s) + 1)))
14752     {
14753       safeStrCpy(ret, s, strlen(s)+1);
14754     }
14755   return ret;
14756 }
14757
14758 char *
14759 StrSavePtr(s, savePtr)
14760      char *s, **savePtr;
14761 {
14762     if (*savePtr) {
14763         free(*savePtr);
14764     }
14765     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14766       safeStrCpy(*savePtr, s, strlen(s)+1);
14767     }
14768     return(*savePtr);
14769 }
14770
14771 char *
14772 PGNDate()
14773 {
14774     time_t clock;
14775     struct tm *tm;
14776     char buf[MSG_SIZ];
14777
14778     clock = time((time_t *)NULL);
14779     tm = localtime(&clock);
14780     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14781             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14782     return StrSave(buf);
14783 }
14784
14785
14786 char *
14787 PositionToFEN(move, overrideCastling)
14788      int move;
14789      char *overrideCastling;
14790 {
14791     int i, j, fromX, fromY, toX, toY;
14792     int whiteToPlay;
14793     char buf[128];
14794     char *p, *q;
14795     int emptycount;
14796     ChessSquare piece;
14797
14798     whiteToPlay = (gameMode == EditPosition) ?
14799       !blackPlaysFirst : (move % 2 == 0);
14800     p = buf;
14801
14802     /* Piece placement data */
14803     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14804         emptycount = 0;
14805         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14806             if (boards[move][i][j] == EmptySquare) {
14807                 emptycount++;
14808             } else { ChessSquare piece = boards[move][i][j];
14809                 if (emptycount > 0) {
14810                     if(emptycount<10) /* [HGM] can be >= 10 */
14811                         *p++ = '0' + emptycount;
14812                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14813                     emptycount = 0;
14814                 }
14815                 if(PieceToChar(piece) == '+') {
14816                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14817                     *p++ = '+';
14818                     piece = (ChessSquare)(DEMOTED piece);
14819                 }
14820                 *p++ = PieceToChar(piece);
14821                 if(p[-1] == '~') {
14822                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14823                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14824                     *p++ = '~';
14825                 }
14826             }
14827         }
14828         if (emptycount > 0) {
14829             if(emptycount<10) /* [HGM] can be >= 10 */
14830                 *p++ = '0' + emptycount;
14831             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14832             emptycount = 0;
14833         }
14834         *p++ = '/';
14835     }
14836     *(p - 1) = ' ';
14837
14838     /* [HGM] print Crazyhouse or Shogi holdings */
14839     if( gameInfo.holdingsWidth ) {
14840         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14841         q = p;
14842         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14843             piece = boards[move][i][BOARD_WIDTH-1];
14844             if( piece != EmptySquare )
14845               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14846                   *p++ = PieceToChar(piece);
14847         }
14848         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14849             piece = boards[move][BOARD_HEIGHT-i-1][0];
14850             if( piece != EmptySquare )
14851               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14852                   *p++ = PieceToChar(piece);
14853         }
14854
14855         if( q == p ) *p++ = '-';
14856         *p++ = ']';
14857         *p++ = ' ';
14858     }
14859
14860     /* Active color */
14861     *p++ = whiteToPlay ? 'w' : 'b';
14862     *p++ = ' ';
14863
14864   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14865     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14866   } else {
14867   if(nrCastlingRights) {
14868      q = p;
14869      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14870        /* [HGM] write directly from rights */
14871            if(boards[move][CASTLING][2] != NoRights &&
14872               boards[move][CASTLING][0] != NoRights   )
14873                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14874            if(boards[move][CASTLING][2] != NoRights &&
14875               boards[move][CASTLING][1] != NoRights   )
14876                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14877            if(boards[move][CASTLING][5] != NoRights &&
14878               boards[move][CASTLING][3] != NoRights   )
14879                 *p++ = boards[move][CASTLING][3] + AAA;
14880            if(boards[move][CASTLING][5] != NoRights &&
14881               boards[move][CASTLING][4] != NoRights   )
14882                 *p++ = boards[move][CASTLING][4] + AAA;
14883      } else {
14884
14885         /* [HGM] write true castling rights */
14886         if( nrCastlingRights == 6 ) {
14887             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14888                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14889             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14890                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14891             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14892                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14893             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14894                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14895         }
14896      }
14897      if (q == p) *p++ = '-'; /* No castling rights */
14898      *p++ = ' ';
14899   }
14900
14901   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14902      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14903     /* En passant target square */
14904     if (move > backwardMostMove) {
14905         fromX = moveList[move - 1][0] - AAA;
14906         fromY = moveList[move - 1][1] - ONE;
14907         toX = moveList[move - 1][2] - AAA;
14908         toY = moveList[move - 1][3] - ONE;
14909         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14910             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14911             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14912             fromX == toX) {
14913             /* 2-square pawn move just happened */
14914             *p++ = toX + AAA;
14915             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14916         } else {
14917             *p++ = '-';
14918         }
14919     } else if(move == backwardMostMove) {
14920         // [HGM] perhaps we should always do it like this, and forget the above?
14921         if((signed char)boards[move][EP_STATUS] >= 0) {
14922             *p++ = boards[move][EP_STATUS] + AAA;
14923             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14924         } else {
14925             *p++ = '-';
14926         }
14927     } else {
14928         *p++ = '-';
14929     }
14930     *p++ = ' ';
14931   }
14932   }
14933
14934     /* [HGM] find reversible plies */
14935     {   int i = 0, j=move;
14936
14937         if (appData.debugMode) { int k;
14938             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14939             for(k=backwardMostMove; k<=forwardMostMove; k++)
14940                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14941
14942         }
14943
14944         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14945         if( j == backwardMostMove ) i += initialRulePlies;
14946         sprintf(p, "%d ", i);
14947         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14948     }
14949     /* Fullmove number */
14950     sprintf(p, "%d", (move / 2) + 1);
14951
14952     return StrSave(buf);
14953 }
14954
14955 Boolean
14956 ParseFEN(board, blackPlaysFirst, fen)
14957     Board board;
14958      int *blackPlaysFirst;
14959      char *fen;
14960 {
14961     int i, j;
14962     char *p, c;
14963     int emptycount;
14964     ChessSquare piece;
14965
14966     p = fen;
14967
14968     /* [HGM] by default clear Crazyhouse holdings, if present */
14969     if(gameInfo.holdingsWidth) {
14970        for(i=0; i<BOARD_HEIGHT; i++) {
14971            board[i][0]             = EmptySquare; /* black holdings */
14972            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14973            board[i][1]             = (ChessSquare) 0; /* black counts */
14974            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14975        }
14976     }
14977
14978     /* Piece placement data */
14979     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14980         j = 0;
14981         for (;;) {
14982             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14983                 if (*p == '/') p++;
14984                 emptycount = gameInfo.boardWidth - j;
14985                 while (emptycount--)
14986                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14987                 break;
14988 #if(BOARD_FILES >= 10)
14989             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14990                 p++; emptycount=10;
14991                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14992                 while (emptycount--)
14993                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14994 #endif
14995             } else if (isdigit(*p)) {
14996                 emptycount = *p++ - '0';
14997                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14998                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14999                 while (emptycount--)
15000                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15001             } else if (*p == '+' || isalpha(*p)) {
15002                 if (j >= gameInfo.boardWidth) return FALSE;
15003                 if(*p=='+') {
15004                     piece = CharToPiece(*++p);
15005                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15006                     piece = (ChessSquare) (PROMOTED piece ); p++;
15007                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15008                 } else piece = CharToPiece(*p++);
15009
15010                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15011                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15012                     piece = (ChessSquare) (PROMOTED piece);
15013                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15014                     p++;
15015                 }
15016                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15017             } else {
15018                 return FALSE;
15019             }
15020         }
15021     }
15022     while (*p == '/' || *p == ' ') p++;
15023
15024     /* [HGM] look for Crazyhouse holdings here */
15025     while(*p==' ') p++;
15026     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15027         if(*p == '[') p++;
15028         if(*p == '-' ) p++; /* empty holdings */ else {
15029             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15030             /* if we would allow FEN reading to set board size, we would   */
15031             /* have to add holdings and shift the board read so far here   */
15032             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15033                 p++;
15034                 if((int) piece >= (int) BlackPawn ) {
15035                     i = (int)piece - (int)BlackPawn;
15036                     i = PieceToNumber((ChessSquare)i);
15037                     if( i >= gameInfo.holdingsSize ) return FALSE;
15038                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15039                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15040                 } else {
15041                     i = (int)piece - (int)WhitePawn;
15042                     i = PieceToNumber((ChessSquare)i);
15043                     if( i >= gameInfo.holdingsSize ) return FALSE;
15044                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15045                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15046                 }
15047             }
15048         }
15049         if(*p == ']') p++;
15050     }
15051
15052     while(*p == ' ') p++;
15053
15054     /* Active color */
15055     c = *p++;
15056     if(appData.colorNickNames) {
15057       if( c == appData.colorNickNames[0] ) c = 'w'; else
15058       if( c == appData.colorNickNames[1] ) c = 'b';
15059     }
15060     switch (c) {
15061       case 'w':
15062         *blackPlaysFirst = FALSE;
15063         break;
15064       case 'b':
15065         *blackPlaysFirst = TRUE;
15066         break;
15067       default:
15068         return FALSE;
15069     }
15070
15071     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15072     /* return the extra info in global variiables             */
15073
15074     /* set defaults in case FEN is incomplete */
15075     board[EP_STATUS] = EP_UNKNOWN;
15076     for(i=0; i<nrCastlingRights; i++ ) {
15077         board[CASTLING][i] =
15078             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15079     }   /* assume possible unless obviously impossible */
15080     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15081     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15082     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15083                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15084     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15085     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15086     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15087                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15088     FENrulePlies = 0;
15089
15090     while(*p==' ') p++;
15091     if(nrCastlingRights) {
15092       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15093           /* castling indicator present, so default becomes no castlings */
15094           for(i=0; i<nrCastlingRights; i++ ) {
15095                  board[CASTLING][i] = NoRights;
15096           }
15097       }
15098       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15099              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15100              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15101              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15102         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15103
15104         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15105             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15106             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15107         }
15108         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15109             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15110         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15111                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15112         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15113                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15114         switch(c) {
15115           case'K':
15116               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15117               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15118               board[CASTLING][2] = whiteKingFile;
15119               break;
15120           case'Q':
15121               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15122               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15123               board[CASTLING][2] = whiteKingFile;
15124               break;
15125           case'k':
15126               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15127               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15128               board[CASTLING][5] = blackKingFile;
15129               break;
15130           case'q':
15131               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15132               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15133               board[CASTLING][5] = blackKingFile;
15134           case '-':
15135               break;
15136           default: /* FRC castlings */
15137               if(c >= 'a') { /* black rights */
15138                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15139                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15140                   if(i == BOARD_RGHT) break;
15141                   board[CASTLING][5] = i;
15142                   c -= AAA;
15143                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15144                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15145                   if(c > i)
15146                       board[CASTLING][3] = c;
15147                   else
15148                       board[CASTLING][4] = c;
15149               } else { /* white rights */
15150                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15151                     if(board[0][i] == WhiteKing) break;
15152                   if(i == BOARD_RGHT) break;
15153                   board[CASTLING][2] = i;
15154                   c -= AAA - 'a' + 'A';
15155                   if(board[0][c] >= WhiteKing) break;
15156                   if(c > i)
15157                       board[CASTLING][0] = c;
15158                   else
15159                       board[CASTLING][1] = c;
15160               }
15161         }
15162       }
15163       for(i=0; i<nrCastlingRights; i++)
15164         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15165     if (appData.debugMode) {
15166         fprintf(debugFP, "FEN castling rights:");
15167         for(i=0; i<nrCastlingRights; i++)
15168         fprintf(debugFP, " %d", board[CASTLING][i]);
15169         fprintf(debugFP, "\n");
15170     }
15171
15172       while(*p==' ') p++;
15173     }
15174
15175     /* read e.p. field in games that know e.p. capture */
15176     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15177        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15178       if(*p=='-') {
15179         p++; board[EP_STATUS] = EP_NONE;
15180       } else {
15181          char c = *p++ - AAA;
15182
15183          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15184          if(*p >= '0' && *p <='9') p++;
15185          board[EP_STATUS] = c;
15186       }
15187     }
15188
15189
15190     if(sscanf(p, "%d", &i) == 1) {
15191         FENrulePlies = i; /* 50-move ply counter */
15192         /* (The move number is still ignored)    */
15193     }
15194
15195     return TRUE;
15196 }
15197
15198 void
15199 EditPositionPasteFEN(char *fen)
15200 {
15201   if (fen != NULL) {
15202     Board initial_position;
15203
15204     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15205       DisplayError(_("Bad FEN position in clipboard"), 0);
15206       return ;
15207     } else {
15208       int savedBlackPlaysFirst = blackPlaysFirst;
15209       EditPositionEvent();
15210       blackPlaysFirst = savedBlackPlaysFirst;
15211       CopyBoard(boards[0], initial_position);
15212       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15213       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15214       DisplayBothClocks();
15215       DrawPosition(FALSE, boards[currentMove]);
15216     }
15217   }
15218 }
15219
15220 static char cseq[12] = "\\   ";
15221
15222 Boolean set_cont_sequence(char *new_seq)
15223 {
15224     int len;
15225     Boolean ret;
15226
15227     // handle bad attempts to set the sequence
15228         if (!new_seq)
15229                 return 0; // acceptable error - no debug
15230
15231     len = strlen(new_seq);
15232     ret = (len > 0) && (len < sizeof(cseq));
15233     if (ret)
15234       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15235     else if (appData.debugMode)
15236       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15237     return ret;
15238 }
15239
15240 /*
15241     reformat a source message so words don't cross the width boundary.  internal
15242     newlines are not removed.  returns the wrapped size (no null character unless
15243     included in source message).  If dest is NULL, only calculate the size required
15244     for the dest buffer.  lp argument indicats line position upon entry, and it's
15245     passed back upon exit.
15246 */
15247 int wrap(char *dest, char *src, int count, int width, int *lp)
15248 {
15249     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15250
15251     cseq_len = strlen(cseq);
15252     old_line = line = *lp;
15253     ansi = len = clen = 0;
15254
15255     for (i=0; i < count; i++)
15256     {
15257         if (src[i] == '\033')
15258             ansi = 1;
15259
15260         // if we hit the width, back up
15261         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15262         {
15263             // store i & len in case the word is too long
15264             old_i = i, old_len = len;
15265
15266             // find the end of the last word
15267             while (i && src[i] != ' ' && src[i] != '\n')
15268             {
15269                 i--;
15270                 len--;
15271             }
15272
15273             // word too long?  restore i & len before splitting it
15274             if ((old_i-i+clen) >= width)
15275             {
15276                 i = old_i;
15277                 len = old_len;
15278             }
15279
15280             // extra space?
15281             if (i && src[i-1] == ' ')
15282                 len--;
15283
15284             if (src[i] != ' ' && src[i] != '\n')
15285             {
15286                 i--;
15287                 if (len)
15288                     len--;
15289             }
15290
15291             // now append the newline and continuation sequence
15292             if (dest)
15293                 dest[len] = '\n';
15294             len++;
15295             if (dest)
15296                 strncpy(dest+len, cseq, cseq_len);
15297             len += cseq_len;
15298             line = cseq_len;
15299             clen = cseq_len;
15300             continue;
15301         }
15302
15303         if (dest)
15304             dest[len] = src[i];
15305         len++;
15306         if (!ansi)
15307             line++;
15308         if (src[i] == '\n')
15309             line = 0;
15310         if (src[i] == 'm')
15311             ansi = 0;
15312     }
15313     if (dest && appData.debugMode)
15314     {
15315         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15316             count, width, line, len, *lp);
15317         show_bytes(debugFP, src, count);
15318         fprintf(debugFP, "\ndest: ");
15319         show_bytes(debugFP, dest, len);
15320         fprintf(debugFP, "\n");
15321     }
15322     *lp = dest ? line : old_line;
15323
15324     return len;
15325 }
15326
15327 // [HGM] vari: routines for shelving variations
15328
15329 void
15330 PushTail(int firstMove, int lastMove)
15331 {
15332         int i, j, nrMoves = lastMove - firstMove;
15333
15334         if(appData.icsActive) { // only in local mode
15335                 forwardMostMove = currentMove; // mimic old ICS behavior
15336                 return;
15337         }
15338         if(storedGames >= MAX_VARIATIONS-1) return;
15339
15340         // push current tail of game on stack
15341         savedResult[storedGames] = gameInfo.result;
15342         savedDetails[storedGames] = gameInfo.resultDetails;
15343         gameInfo.resultDetails = NULL;
15344         savedFirst[storedGames] = firstMove;
15345         savedLast [storedGames] = lastMove;
15346         savedFramePtr[storedGames] = framePtr;
15347         framePtr -= nrMoves; // reserve space for the boards
15348         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15349             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15350             for(j=0; j<MOVE_LEN; j++)
15351                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15352             for(j=0; j<2*MOVE_LEN; j++)
15353                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15354             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15355             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15356             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15357             pvInfoList[firstMove+i-1].depth = 0;
15358             commentList[framePtr+i] = commentList[firstMove+i];
15359             commentList[firstMove+i] = NULL;
15360         }
15361
15362         storedGames++;
15363         forwardMostMove = firstMove; // truncate game so we can start variation
15364         if(storedGames == 1) GreyRevert(FALSE);
15365 }
15366
15367 Boolean
15368 PopTail(Boolean annotate)
15369 {
15370         int i, j, nrMoves;
15371         char buf[8000], moveBuf[20];
15372
15373         if(appData.icsActive) return FALSE; // only in local mode
15374         if(!storedGames) return FALSE; // sanity
15375         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15376
15377         storedGames--;
15378         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15379         nrMoves = savedLast[storedGames] - currentMove;
15380         if(annotate) {
15381                 int cnt = 10;
15382                 if(!WhiteOnMove(currentMove))
15383                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15384                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15385                 for(i=currentMove; i<forwardMostMove; i++) {
15386                         if(WhiteOnMove(i))
15387                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15388                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15389                         strcat(buf, moveBuf);
15390                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15391                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15392                 }
15393                 strcat(buf, ")");
15394         }
15395         for(i=1; i<=nrMoves; i++) { // copy last variation back
15396             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15397             for(j=0; j<MOVE_LEN; j++)
15398                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15399             for(j=0; j<2*MOVE_LEN; j++)
15400                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15401             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15402             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15403             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15404             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15405             commentList[currentMove+i] = commentList[framePtr+i];
15406             commentList[framePtr+i] = NULL;
15407         }
15408         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15409         framePtr = savedFramePtr[storedGames];
15410         gameInfo.result = savedResult[storedGames];
15411         if(gameInfo.resultDetails != NULL) {
15412             free(gameInfo.resultDetails);
15413       }
15414         gameInfo.resultDetails = savedDetails[storedGames];
15415         forwardMostMove = currentMove + nrMoves;
15416         if(storedGames == 0) GreyRevert(TRUE);
15417         return TRUE;
15418 }
15419
15420 void
15421 CleanupTail()
15422 {       // remove all shelved variations
15423         int i;
15424         for(i=0; i<storedGames; i++) {
15425             if(savedDetails[i])
15426                 free(savedDetails[i]);
15427             savedDetails[i] = NULL;
15428         }
15429         for(i=framePtr; i<MAX_MOVES; i++) {
15430                 if(commentList[i]) free(commentList[i]);
15431                 commentList[i] = NULL;
15432         }
15433         framePtr = MAX_MOVES-1;
15434         storedGames = 0;
15435 }
15436
15437 void
15438 LoadVariation(int index, char *text)
15439 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15440         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15441         int level = 0, move;
15442
15443         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15444         // first find outermost bracketing variation
15445         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15446             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15447                 if(*p == '{') wait = '}'; else
15448                 if(*p == '[') wait = ']'; else
15449                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15450                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15451             }
15452             if(*p == wait) wait = NULLCHAR; // closing ]} found
15453             p++;
15454         }
15455         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15456         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15457         end[1] = NULLCHAR; // clip off comment beyond variation
15458         ToNrEvent(currentMove-1);
15459         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15460         // kludge: use ParsePV() to append variation to game
15461         move = currentMove;
15462         ParsePV(start, TRUE);
15463         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15464         ClearPremoveHighlights();
15465         CommentPopDown();
15466         ToNrEvent(currentMove+1);
15467 }
15468