847b6092bd1cb911c70224a825aed714217148c0
[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     if(storeComments)
4995         CoordsToAlgebraic(boards[endPV - 1],
4996                              PosFlags(endPV - 1),
4997                              fromY, fromX, toY, toX, promoChar,
4998                              parseList[endPV - 1]);
4999     else
5000         parseList[endPV-1][0] = NULLCHAR;
5001   } while(valid);
5002   currentMove = endPV;
5003   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5004   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5005                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5006   DrawPosition(TRUE, boards[currentMove]);
5007 }
5008
5009 static int lastX, lastY;
5010
5011 Boolean
5012 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5013 {
5014         int startPV;
5015         char *p;
5016
5017         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5018         lastX = x; lastY = y;
5019         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5020         startPV = index;
5021         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5022         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5023         index = startPV;
5024         do{ while(buf[index] && buf[index] != '\n') index++;
5025         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5026         buf[index] = 0;
5027         ParsePV(buf+startPV, FALSE);
5028         *start = startPV; *end = index-1;
5029         return TRUE;
5030 }
5031
5032 Boolean
5033 LoadPV(int x, int y)
5034 { // called on right mouse click to load PV
5035   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5036   lastX = x; lastY = y;
5037   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5038   return TRUE;
5039 }
5040
5041 void
5042 UnLoadPV()
5043 {
5044   if(endPV < 0) return;
5045   endPV = -1;
5046   currentMove = forwardMostMove;
5047   ClearPremoveHighlights();
5048   DrawPosition(TRUE, boards[currentMove]);
5049 }
5050
5051 void
5052 MovePV(int x, int y, int h)
5053 { // step through PV based on mouse coordinates (called on mouse move)
5054   int margin = h>>3, step = 0;
5055
5056   if(endPV < 0) return;
5057   // we must somehow check if right button is still down (might be released off board!)
5058   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5059   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5060   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5061   if(!step) return;
5062   lastX = x; lastY = y;
5063   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5064   currentMove += step;
5065   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5066   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5067                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5068   DrawPosition(FALSE, boards[currentMove]);
5069 }
5070
5071
5072 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5073 // All positions will have equal probability, but the current method will not provide a unique
5074 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5075 #define DARK 1
5076 #define LITE 2
5077 #define ANY 3
5078
5079 int squaresLeft[4];
5080 int piecesLeft[(int)BlackPawn];
5081 int seed, nrOfShuffles;
5082
5083 void GetPositionNumber()
5084 {       // sets global variable seed
5085         int i;
5086
5087         seed = appData.defaultFrcPosition;
5088         if(seed < 0) { // randomize based on time for negative FRC position numbers
5089                 for(i=0; i<50; i++) seed += random();
5090                 seed = random() ^ random() >> 8 ^ random() << 8;
5091                 if(seed<0) seed = -seed;
5092         }
5093 }
5094
5095 int put(Board board, int pieceType, int rank, int n, int shade)
5096 // put the piece on the (n-1)-th empty squares of the given shade
5097 {
5098         int i;
5099
5100         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5101                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5102                         board[rank][i] = (ChessSquare) pieceType;
5103                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5104                         squaresLeft[ANY]--;
5105                         piecesLeft[pieceType]--;
5106                         return i;
5107                 }
5108         }
5109         return -1;
5110 }
5111
5112
5113 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5114 // calculate where the next piece goes, (any empty square), and put it there
5115 {
5116         int i;
5117
5118         i = seed % squaresLeft[shade];
5119         nrOfShuffles *= squaresLeft[shade];
5120         seed /= squaresLeft[shade];
5121         put(board, pieceType, rank, i, shade);
5122 }
5123
5124 void AddTwoPieces(Board board, int pieceType, int rank)
5125 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5126 {
5127         int i, n=squaresLeft[ANY], j=n-1, k;
5128
5129         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5130         i = seed % k;  // pick one
5131         nrOfShuffles *= k;
5132         seed /= k;
5133         while(i >= j) i -= j--;
5134         j = n - 1 - j; i += j;
5135         put(board, pieceType, rank, j, ANY);
5136         put(board, pieceType, rank, i, ANY);
5137 }
5138
5139 void SetUpShuffle(Board board, int number)
5140 {
5141         int i, p, first=1;
5142
5143         GetPositionNumber(); nrOfShuffles = 1;
5144
5145         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5146         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5147         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5148
5149         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5150
5151         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5152             p = (int) board[0][i];
5153             if(p < (int) BlackPawn) piecesLeft[p] ++;
5154             board[0][i] = EmptySquare;
5155         }
5156
5157         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5158             // shuffles restricted to allow normal castling put KRR first
5159             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5160                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5161             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5162                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5163             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5164                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5165             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5166                 put(board, WhiteRook, 0, 0, ANY);
5167             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5168         }
5169
5170         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5171             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5172             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5173                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5174                 while(piecesLeft[p] >= 2) {
5175                     AddOnePiece(board, p, 0, LITE);
5176                     AddOnePiece(board, p, 0, DARK);
5177                 }
5178                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5179             }
5180
5181         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5182             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5183             // but we leave King and Rooks for last, to possibly obey FRC restriction
5184             if(p == (int)WhiteRook) continue;
5185             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5186             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5187         }
5188
5189         // now everything is placed, except perhaps King (Unicorn) and Rooks
5190
5191         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5192             // Last King gets castling rights
5193             while(piecesLeft[(int)WhiteUnicorn]) {
5194                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5195                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5196             }
5197
5198             while(piecesLeft[(int)WhiteKing]) {
5199                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5200                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5201             }
5202
5203
5204         } else {
5205             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5206             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5207         }
5208
5209         // Only Rooks can be left; simply place them all
5210         while(piecesLeft[(int)WhiteRook]) {
5211                 i = put(board, WhiteRook, 0, 0, ANY);
5212                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5213                         if(first) {
5214                                 first=0;
5215                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5216                         }
5217                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5218                 }
5219         }
5220         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5221             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5222         }
5223
5224         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5225 }
5226
5227 int SetCharTable( char *table, const char * map )
5228 /* [HGM] moved here from winboard.c because of its general usefulness */
5229 /*       Basically a safe strcpy that uses the last character as King */
5230 {
5231     int result = FALSE; int NrPieces;
5232
5233     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5234                     && NrPieces >= 12 && !(NrPieces&1)) {
5235         int i; /* [HGM] Accept even length from 12 to 34 */
5236
5237         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5238         for( i=0; i<NrPieces/2-1; i++ ) {
5239             table[i] = map[i];
5240             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5241         }
5242         table[(int) WhiteKing]  = map[NrPieces/2-1];
5243         table[(int) BlackKing]  = map[NrPieces-1];
5244
5245         result = TRUE;
5246     }
5247
5248     return result;
5249 }
5250
5251 void Prelude(Board board)
5252 {       // [HGM] superchess: random selection of exo-pieces
5253         int i, j, k; ChessSquare p;
5254         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5255
5256         GetPositionNumber(); // use FRC position number
5257
5258         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5259             SetCharTable(pieceToChar, appData.pieceToCharTable);
5260             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5261                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5262         }
5263
5264         j = seed%4;                 seed /= 4;
5265         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5266         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5267         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5268         j = seed%3 + (seed%3 >= j); seed /= 3;
5269         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5270         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5271         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5272         j = seed%3;                 seed /= 3;
5273         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5274         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5275         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5276         j = seed%2 + (seed%2 >= j); seed /= 2;
5277         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5278         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5279         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5280         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5281         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5282         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5283         put(board, exoPieces[0],    0, 0, ANY);
5284         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5285 }
5286
5287 void
5288 InitPosition(redraw)
5289      int redraw;
5290 {
5291     ChessSquare (* pieces)[BOARD_FILES];
5292     int i, j, pawnRow, overrule,
5293     oldx = gameInfo.boardWidth,
5294     oldy = gameInfo.boardHeight,
5295     oldh = gameInfo.holdingsWidth,
5296     oldv = gameInfo.variant;
5297
5298     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5299
5300     /* [AS] Initialize pv info list [HGM] and game status */
5301     {
5302         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5303             pvInfoList[i].depth = 0;
5304             boards[i][EP_STATUS] = EP_NONE;
5305             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5306         }
5307
5308         initialRulePlies = 0; /* 50-move counter start */
5309
5310         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5311         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5312     }
5313
5314
5315     /* [HGM] logic here is completely changed. In stead of full positions */
5316     /* the initialized data only consist of the two backranks. The switch */
5317     /* selects which one we will use, which is than copied to the Board   */
5318     /* initialPosition, which for the rest is initialized by Pawns and    */
5319     /* empty squares. This initial position is then copied to boards[0],  */
5320     /* possibly after shuffling, so that it remains available.            */
5321
5322     gameInfo.holdingsWidth = 0; /* default board sizes */
5323     gameInfo.boardWidth    = 8;
5324     gameInfo.boardHeight   = 8;
5325     gameInfo.holdingsSize  = 0;
5326     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5327     for(i=0; i<BOARD_FILES-2; i++)
5328       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5329     initialPosition[EP_STATUS] = EP_NONE;
5330     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5331     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5332          SetCharTable(pieceNickName, appData.pieceNickNames);
5333     else SetCharTable(pieceNickName, "............");
5334     pieces = FIDEArray;
5335
5336     switch (gameInfo.variant) {
5337     case VariantFischeRandom:
5338       shuffleOpenings = TRUE;
5339     default:
5340       break;
5341     case VariantShatranj:
5342       pieces = ShatranjArray;
5343       nrCastlingRights = 0;
5344       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5345       break;
5346     case VariantMakruk:
5347       pieces = makrukArray;
5348       nrCastlingRights = 0;
5349       startedFromSetupPosition = TRUE;
5350       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5351       break;
5352     case VariantTwoKings:
5353       pieces = twoKingsArray;
5354       break;
5355     case VariantCapaRandom:
5356       shuffleOpenings = TRUE;
5357     case VariantCapablanca:
5358       pieces = CapablancaArray;
5359       gameInfo.boardWidth = 10;
5360       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5361       break;
5362     case VariantGothic:
5363       pieces = GothicArray;
5364       gameInfo.boardWidth = 10;
5365       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5366       break;
5367     case VariantSChess:
5368       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5369       gameInfo.holdingsSize = 7;
5370       break;
5371     case VariantJanus:
5372       pieces = JanusArray;
5373       gameInfo.boardWidth = 10;
5374       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5375       nrCastlingRights = 6;
5376         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5377         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5378         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5379         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5380         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5381         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5382       break;
5383     case VariantFalcon:
5384       pieces = FalconArray;
5385       gameInfo.boardWidth = 10;
5386       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5387       break;
5388     case VariantXiangqi:
5389       pieces = XiangqiArray;
5390       gameInfo.boardWidth  = 9;
5391       gameInfo.boardHeight = 10;
5392       nrCastlingRights = 0;
5393       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5394       break;
5395     case VariantShogi:
5396       pieces = ShogiArray;
5397       gameInfo.boardWidth  = 9;
5398       gameInfo.boardHeight = 9;
5399       gameInfo.holdingsSize = 7;
5400       nrCastlingRights = 0;
5401       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5402       break;
5403     case VariantCourier:
5404       pieces = CourierArray;
5405       gameInfo.boardWidth  = 12;
5406       nrCastlingRights = 0;
5407       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5408       break;
5409     case VariantKnightmate:
5410       pieces = KnightmateArray;
5411       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5412       break;
5413     case VariantFairy:
5414       pieces = fairyArray;
5415       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5416       break;
5417     case VariantGreat:
5418       pieces = GreatArray;
5419       gameInfo.boardWidth = 10;
5420       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5421       gameInfo.holdingsSize = 8;
5422       break;
5423     case VariantSuper:
5424       pieces = FIDEArray;
5425       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5426       gameInfo.holdingsSize = 8;
5427       startedFromSetupPosition = TRUE;
5428       break;
5429     case VariantCrazyhouse:
5430     case VariantBughouse:
5431       pieces = FIDEArray;
5432       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5433       gameInfo.holdingsSize = 5;
5434       break;
5435     case VariantWildCastle:
5436       pieces = FIDEArray;
5437       /* !!?shuffle with kings guaranteed to be on d or e file */
5438       shuffleOpenings = 1;
5439       break;
5440     case VariantNoCastle:
5441       pieces = FIDEArray;
5442       nrCastlingRights = 0;
5443       /* !!?unconstrained back-rank shuffle */
5444       shuffleOpenings = 1;
5445       break;
5446     }
5447
5448     overrule = 0;
5449     if(appData.NrFiles >= 0) {
5450         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5451         gameInfo.boardWidth = appData.NrFiles;
5452     }
5453     if(appData.NrRanks >= 0) {
5454         gameInfo.boardHeight = appData.NrRanks;
5455     }
5456     if(appData.holdingsSize >= 0) {
5457         i = appData.holdingsSize;
5458         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5459         gameInfo.holdingsSize = i;
5460     }
5461     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5462     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5463         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5464
5465     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5466     if(pawnRow < 1) pawnRow = 1;
5467     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5468
5469     /* User pieceToChar list overrules defaults */
5470     if(appData.pieceToCharTable != NULL)
5471         SetCharTable(pieceToChar, appData.pieceToCharTable);
5472
5473     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5474
5475         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5476             s = (ChessSquare) 0; /* account holding counts in guard band */
5477         for( i=0; i<BOARD_HEIGHT; i++ )
5478             initialPosition[i][j] = s;
5479
5480         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5481         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5482         initialPosition[pawnRow][j] = WhitePawn;
5483         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5484         if(gameInfo.variant == VariantXiangqi) {
5485             if(j&1) {
5486                 initialPosition[pawnRow][j] =
5487                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5488                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5489                    initialPosition[2][j] = WhiteCannon;
5490                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5491                 }
5492             }
5493         }
5494         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5495     }
5496     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5497
5498             j=BOARD_LEFT+1;
5499             initialPosition[1][j] = WhiteBishop;
5500             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5501             j=BOARD_RGHT-2;
5502             initialPosition[1][j] = WhiteRook;
5503             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5504     }
5505
5506     if( nrCastlingRights == -1) {
5507         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5508         /*       This sets default castling rights from none to normal corners   */
5509         /* Variants with other castling rights must set them themselves above    */
5510         nrCastlingRights = 6;
5511
5512         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5513         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5514         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5515         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5516         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5517         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5518      }
5519
5520      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5521      if(gameInfo.variant == VariantGreat) { // promotion commoners
5522         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5523         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5524         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5525         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5526      }
5527      if( gameInfo.variant == VariantSChess ) {
5528       initialPosition[1][0] = BlackMarshall;
5529       initialPosition[2][0] = BlackAngel;
5530       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5531       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5532       initialPosition[1][1] = initialPosition[2][1] = 
5533       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5534      }
5535   if (appData.debugMode) {
5536     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5537   }
5538     if(shuffleOpenings) {
5539         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5540         startedFromSetupPosition = TRUE;
5541     }
5542     if(startedFromPositionFile) {
5543       /* [HGM] loadPos: use PositionFile for every new game */
5544       CopyBoard(initialPosition, filePosition);
5545       for(i=0; i<nrCastlingRights; i++)
5546           initialRights[i] = filePosition[CASTLING][i];
5547       startedFromSetupPosition = TRUE;
5548     }
5549
5550     CopyBoard(boards[0], initialPosition);
5551
5552     if(oldx != gameInfo.boardWidth ||
5553        oldy != gameInfo.boardHeight ||
5554        oldh != gameInfo.holdingsWidth
5555 #ifdef GOTHIC
5556        || oldv == VariantGothic ||        // For licensing popups
5557        gameInfo.variant == VariantGothic
5558 #endif
5559 #ifdef FALCON
5560        || oldv == VariantFalcon ||
5561        gameInfo.variant == VariantFalcon
5562 #endif
5563                                          )
5564             InitDrawingSizes(-2 ,0);
5565
5566     if (redraw)
5567       DrawPosition(TRUE, boards[currentMove]);
5568 }
5569
5570 void
5571 SendBoard(cps, moveNum)
5572      ChessProgramState *cps;
5573      int moveNum;
5574 {
5575     char message[MSG_SIZ];
5576
5577     if (cps->useSetboard) {
5578       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5579       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5580       SendToProgram(message, cps);
5581       free(fen);
5582
5583     } else {
5584       ChessSquare *bp;
5585       int i, j;
5586       /* Kludge to set black to move, avoiding the troublesome and now
5587        * deprecated "black" command.
5588        */
5589       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5590         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5591
5592       SendToProgram("edit\n", cps);
5593       SendToProgram("#\n", cps);
5594       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5595         bp = &boards[moveNum][i][BOARD_LEFT];
5596         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5597           if ((int) *bp < (int) BlackPawn) {
5598             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5599                     AAA + j, ONE + i);
5600             if(message[0] == '+' || message[0] == '~') {
5601               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5602                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5603                         AAA + j, ONE + i);
5604             }
5605             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5606                 message[1] = BOARD_RGHT   - 1 - j + '1';
5607                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5608             }
5609             SendToProgram(message, cps);
5610           }
5611         }
5612       }
5613
5614       SendToProgram("c\n", cps);
5615       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5616         bp = &boards[moveNum][i][BOARD_LEFT];
5617         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5618           if (((int) *bp != (int) EmptySquare)
5619               && ((int) *bp >= (int) BlackPawn)) {
5620             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5621                     AAA + j, ONE + i);
5622             if(message[0] == '+' || message[0] == '~') {
5623               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5624                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5625                         AAA + j, ONE + i);
5626             }
5627             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5628                 message[1] = BOARD_RGHT   - 1 - j + '1';
5629                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5630             }
5631             SendToProgram(message, cps);
5632           }
5633         }
5634       }
5635
5636       SendToProgram(".\n", cps);
5637     }
5638     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5639 }
5640
5641 static int autoQueen; // [HGM] oneclick
5642
5643 int
5644 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5645 {
5646     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5647     /* [HGM] add Shogi promotions */
5648     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5649     ChessSquare piece;
5650     ChessMove moveType;
5651     Boolean premove;
5652
5653     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5654     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5655
5656     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5657       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5658         return FALSE;
5659
5660     piece = boards[currentMove][fromY][fromX];
5661     if(gameInfo.variant == VariantShogi) {
5662         promotionZoneSize = BOARD_HEIGHT/3;
5663         highestPromotingPiece = (int)WhiteFerz;
5664     } else if(gameInfo.variant == VariantMakruk) {
5665         promotionZoneSize = 3;
5666     }
5667
5668     // next weed out all moves that do not touch the promotion zone at all
5669     if((int)piece >= BlackPawn) {
5670         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5671              return FALSE;
5672         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5673     } else {
5674         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5675            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5676     }
5677
5678     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5679
5680     // weed out mandatory Shogi promotions
5681     if(gameInfo.variant == VariantShogi) {
5682         if(piece >= BlackPawn) {
5683             if(toY == 0 && piece == BlackPawn ||
5684                toY == 0 && piece == BlackQueen ||
5685                toY <= 1 && piece == BlackKnight) {
5686                 *promoChoice = '+';
5687                 return FALSE;
5688             }
5689         } else {
5690             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5691                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5692                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5693                 *promoChoice = '+';
5694                 return FALSE;
5695             }
5696         }
5697     }
5698
5699     // weed out obviously illegal Pawn moves
5700     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5701         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5702         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5703         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5704         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5705         // note we are not allowed to test for valid (non-)capture, due to premove
5706     }
5707
5708     // we either have a choice what to promote to, or (in Shogi) whether to promote
5709     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5710         *promoChoice = PieceToChar(BlackFerz);  // no choice
5711         return FALSE;
5712     }
5713     // no sense asking what we must promote to if it is going to explode...
5714     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5715         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5716         return FALSE;
5717     }
5718     if(autoQueen) { // predetermined
5719         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5720              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5721         else *promoChoice = PieceToChar(BlackQueen);
5722         return FALSE;
5723     }
5724
5725     // suppress promotion popup on illegal moves that are not premoves
5726     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5727               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5728     if(appData.testLegality && !premove) {
5729         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5730                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5731         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5732             return FALSE;
5733     }
5734
5735     return TRUE;
5736 }
5737
5738 int
5739 InPalace(row, column)
5740      int row, column;
5741 {   /* [HGM] for Xiangqi */
5742     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5743          column < (BOARD_WIDTH + 4)/2 &&
5744          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5745     return FALSE;
5746 }
5747
5748 int
5749 PieceForSquare (x, y)
5750      int x;
5751      int y;
5752 {
5753   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5754      return -1;
5755   else
5756      return boards[currentMove][y][x];
5757 }
5758
5759 int
5760 OKToStartUserMove(x, y)
5761      int x, y;
5762 {
5763     ChessSquare from_piece;
5764     int white_piece;
5765
5766     if (matchMode) return FALSE;
5767     if (gameMode == EditPosition) return TRUE;
5768
5769     if (x >= 0 && y >= 0)
5770       from_piece = boards[currentMove][y][x];
5771     else
5772       from_piece = EmptySquare;
5773
5774     if (from_piece == EmptySquare) return FALSE;
5775
5776     white_piece = (int)from_piece >= (int)WhitePawn &&
5777       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5778
5779     switch (gameMode) {
5780       case PlayFromGameFile:
5781       case AnalyzeFile:
5782       case TwoMachinesPlay:
5783       case EndOfGame:
5784         return FALSE;
5785
5786       case IcsObserving:
5787       case IcsIdle:
5788         return FALSE;
5789
5790       case MachinePlaysWhite:
5791       case IcsPlayingBlack:
5792         if (appData.zippyPlay) return FALSE;
5793         if (white_piece) {
5794             DisplayMoveError(_("You are playing Black"));
5795             return FALSE;
5796         }
5797         break;
5798
5799       case MachinePlaysBlack:
5800       case IcsPlayingWhite:
5801         if (appData.zippyPlay) return FALSE;
5802         if (!white_piece) {
5803             DisplayMoveError(_("You are playing White"));
5804             return FALSE;
5805         }
5806         break;
5807
5808       case EditGame:
5809         if (!white_piece && WhiteOnMove(currentMove)) {
5810             DisplayMoveError(_("It is White's turn"));
5811             return FALSE;
5812         }
5813         if (white_piece && !WhiteOnMove(currentMove)) {
5814             DisplayMoveError(_("It is Black's turn"));
5815             return FALSE;
5816         }
5817         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5818             /* Editing correspondence game history */
5819             /* Could disallow this or prompt for confirmation */
5820             cmailOldMove = -1;
5821         }
5822         break;
5823
5824       case BeginningOfGame:
5825         if (appData.icsActive) return FALSE;
5826         if (!appData.noChessProgram) {
5827             if (!white_piece) {
5828                 DisplayMoveError(_("You are playing White"));
5829                 return FALSE;
5830             }
5831         }
5832         break;
5833
5834       case Training:
5835         if (!white_piece && WhiteOnMove(currentMove)) {
5836             DisplayMoveError(_("It is White's turn"));
5837             return FALSE;
5838         }
5839         if (white_piece && !WhiteOnMove(currentMove)) {
5840             DisplayMoveError(_("It is Black's turn"));
5841             return FALSE;
5842         }
5843         break;
5844
5845       default:
5846       case IcsExamining:
5847         break;
5848     }
5849     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5850         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5851         && gameMode != AnalyzeFile && gameMode != Training) {
5852         DisplayMoveError(_("Displayed position is not current"));
5853         return FALSE;
5854     }
5855     return TRUE;
5856 }
5857
5858 Boolean
5859 OnlyMove(int *x, int *y, Boolean captures) {
5860     DisambiguateClosure cl;
5861     if (appData.zippyPlay) return FALSE;
5862     switch(gameMode) {
5863       case MachinePlaysBlack:
5864       case IcsPlayingWhite:
5865       case BeginningOfGame:
5866         if(!WhiteOnMove(currentMove)) return FALSE;
5867         break;
5868       case MachinePlaysWhite:
5869       case IcsPlayingBlack:
5870         if(WhiteOnMove(currentMove)) return FALSE;
5871         break;
5872       case EditGame:
5873         break;
5874       default:
5875         return FALSE;
5876     }
5877     cl.pieceIn = EmptySquare;
5878     cl.rfIn = *y;
5879     cl.ffIn = *x;
5880     cl.rtIn = -1;
5881     cl.ftIn = -1;
5882     cl.promoCharIn = NULLCHAR;
5883     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5884     if( cl.kind == NormalMove ||
5885         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5886         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5887         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5888       fromX = cl.ff;
5889       fromY = cl.rf;
5890       *x = cl.ft;
5891       *y = cl.rt;
5892       return TRUE;
5893     }
5894     if(cl.kind != ImpossibleMove) return FALSE;
5895     cl.pieceIn = EmptySquare;
5896     cl.rfIn = -1;
5897     cl.ffIn = -1;
5898     cl.rtIn = *y;
5899     cl.ftIn = *x;
5900     cl.promoCharIn = NULLCHAR;
5901     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5902     if( cl.kind == NormalMove ||
5903         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5904         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5905         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5906       fromX = cl.ff;
5907       fromY = cl.rf;
5908       *x = cl.ft;
5909       *y = cl.rt;
5910       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5911       return TRUE;
5912     }
5913     return FALSE;
5914 }
5915
5916 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5917 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5918 int lastLoadGameUseList = FALSE;
5919 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5920 ChessMove lastLoadGameStart = EndOfFile;
5921
5922 void
5923 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5924      int fromX, fromY, toX, toY;
5925      int promoChar;
5926 {
5927     ChessMove moveType;
5928     ChessSquare pdown, pup;
5929
5930     /* Check if the user is playing in turn.  This is complicated because we
5931        let the user "pick up" a piece before it is his turn.  So the piece he
5932        tried to pick up may have been captured by the time he puts it down!
5933        Therefore we use the color the user is supposed to be playing in this
5934        test, not the color of the piece that is currently on the starting
5935        square---except in EditGame mode, where the user is playing both
5936        sides; fortunately there the capture race can't happen.  (It can
5937        now happen in IcsExamining mode, but that's just too bad.  The user
5938        will get a somewhat confusing message in that case.)
5939        */
5940
5941     switch (gameMode) {
5942       case PlayFromGameFile:
5943       case AnalyzeFile:
5944       case TwoMachinesPlay:
5945       case EndOfGame:
5946       case IcsObserving:
5947       case IcsIdle:
5948         /* We switched into a game mode where moves are not accepted,
5949            perhaps while the mouse button was down. */
5950         return;
5951
5952       case MachinePlaysWhite:
5953         /* User is moving for Black */
5954         if (WhiteOnMove(currentMove)) {
5955             DisplayMoveError(_("It is White's turn"));
5956             return;
5957         }
5958         break;
5959
5960       case MachinePlaysBlack:
5961         /* User is moving for White */
5962         if (!WhiteOnMove(currentMove)) {
5963             DisplayMoveError(_("It is Black's turn"));
5964             return;
5965         }
5966         break;
5967
5968       case EditGame:
5969       case IcsExamining:
5970       case BeginningOfGame:
5971       case AnalyzeMode:
5972       case Training:
5973         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5974             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5975             /* User is moving for Black */
5976             if (WhiteOnMove(currentMove)) {
5977                 DisplayMoveError(_("It is White's turn"));
5978                 return;
5979             }
5980         } else {
5981             /* User is moving for White */
5982             if (!WhiteOnMove(currentMove)) {
5983                 DisplayMoveError(_("It is Black's turn"));
5984                 return;
5985             }
5986         }
5987         break;
5988
5989       case IcsPlayingBlack:
5990         /* User is moving for Black */
5991         if (WhiteOnMove(currentMove)) {
5992             if (!appData.premove) {
5993                 DisplayMoveError(_("It is White's turn"));
5994             } else if (toX >= 0 && toY >= 0) {
5995                 premoveToX = toX;
5996                 premoveToY = toY;
5997                 premoveFromX = fromX;
5998                 premoveFromY = fromY;
5999                 premovePromoChar = promoChar;
6000                 gotPremove = 1;
6001                 if (appData.debugMode)
6002                     fprintf(debugFP, "Got premove: fromX %d,"
6003                             "fromY %d, toX %d, toY %d\n",
6004                             fromX, fromY, toX, toY);
6005             }
6006             return;
6007         }
6008         break;
6009
6010       case IcsPlayingWhite:
6011         /* User is moving for White */
6012         if (!WhiteOnMove(currentMove)) {
6013             if (!appData.premove) {
6014                 DisplayMoveError(_("It is Black's turn"));
6015             } else if (toX >= 0 && toY >= 0) {
6016                 premoveToX = toX;
6017                 premoveToY = toY;
6018                 premoveFromX = fromX;
6019                 premoveFromY = fromY;
6020                 premovePromoChar = promoChar;
6021                 gotPremove = 1;
6022                 if (appData.debugMode)
6023                     fprintf(debugFP, "Got premove: fromX %d,"
6024                             "fromY %d, toX %d, toY %d\n",
6025                             fromX, fromY, toX, toY);
6026             }
6027             return;
6028         }
6029         break;
6030
6031       default:
6032         break;
6033
6034       case EditPosition:
6035         /* EditPosition, empty square, or different color piece;
6036            click-click move is possible */
6037         if (toX == -2 || toY == -2) {
6038             boards[0][fromY][fromX] = EmptySquare;
6039             DrawPosition(FALSE, boards[currentMove]);
6040             return;
6041         } else if (toX >= 0 && toY >= 0) {
6042             boards[0][toY][toX] = boards[0][fromY][fromX];
6043             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6044                 if(boards[0][fromY][0] != EmptySquare) {
6045                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6046                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6047                 }
6048             } else
6049             if(fromX == BOARD_RGHT+1) {
6050                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6051                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6052                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6053                 }
6054             } else
6055             boards[0][fromY][fromX] = EmptySquare;
6056             DrawPosition(FALSE, boards[currentMove]);
6057             return;
6058         }
6059         return;
6060     }
6061
6062     if(toX < 0 || toY < 0) return;
6063     pdown = boards[currentMove][fromY][fromX];
6064     pup = boards[currentMove][toY][toX];
6065
6066     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6067     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6068          if( pup != EmptySquare ) return;
6069          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6070            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6071                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6072            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6073            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6074            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6075            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6076          fromY = DROP_RANK;
6077     }
6078
6079     /* [HGM] always test for legality, to get promotion info */
6080     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6081                                          fromY, fromX, toY, toX, promoChar);
6082     /* [HGM] but possibly ignore an IllegalMove result */
6083     if (appData.testLegality) {
6084         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6085             DisplayMoveError(_("Illegal move"));
6086             return;
6087         }
6088     }
6089
6090     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6091 }
6092
6093 /* Common tail of UserMoveEvent and DropMenuEvent */
6094 int
6095 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6096      ChessMove moveType;
6097      int fromX, fromY, toX, toY;
6098      /*char*/int promoChar;
6099 {
6100     char *bookHit = 0;
6101
6102     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6103         // [HGM] superchess: suppress promotions to non-available piece
6104         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6105         if(WhiteOnMove(currentMove)) {
6106             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6107         } else {
6108             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6109         }
6110     }
6111
6112     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6113        move type in caller when we know the move is a legal promotion */
6114     if(moveType == NormalMove && promoChar)
6115         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6116
6117     /* [HGM] <popupFix> The following if has been moved here from
6118        UserMoveEvent(). Because it seemed to belong here (why not allow
6119        piece drops in training games?), and because it can only be
6120        performed after it is known to what we promote. */
6121     if (gameMode == Training) {
6122       /* compare the move played on the board to the next move in the
6123        * game. If they match, display the move and the opponent's response.
6124        * If they don't match, display an error message.
6125        */
6126       int saveAnimate;
6127       Board testBoard;
6128       CopyBoard(testBoard, boards[currentMove]);
6129       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6130
6131       if (CompareBoards(testBoard, boards[currentMove+1])) {
6132         ForwardInner(currentMove+1);
6133
6134         /* Autoplay the opponent's response.
6135          * if appData.animate was TRUE when Training mode was entered,
6136          * the response will be animated.
6137          */
6138         saveAnimate = appData.animate;
6139         appData.animate = animateTraining;
6140         ForwardInner(currentMove+1);
6141         appData.animate = saveAnimate;
6142
6143         /* check for the end of the game */
6144         if (currentMove >= forwardMostMove) {
6145           gameMode = PlayFromGameFile;
6146           ModeHighlight();
6147           SetTrainingModeOff();
6148           DisplayInformation(_("End of game"));
6149         }
6150       } else {
6151         DisplayError(_("Incorrect move"), 0);
6152       }
6153       return 1;
6154     }
6155
6156   /* Ok, now we know that the move is good, so we can kill
6157      the previous line in Analysis Mode */
6158   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6159                                 && currentMove < forwardMostMove) {
6160     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6161     else forwardMostMove = currentMove;
6162   }
6163
6164   /* If we need the chess program but it's dead, restart it */
6165   ResurrectChessProgram();
6166
6167   /* A user move restarts a paused game*/
6168   if (pausing)
6169     PauseEvent();
6170
6171   thinkOutput[0] = NULLCHAR;
6172
6173   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6174
6175   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6176
6177   if (gameMode == BeginningOfGame) {
6178     if (appData.noChessProgram) {
6179       gameMode = EditGame;
6180       SetGameInfo();
6181     } else {
6182       char buf[MSG_SIZ];
6183       gameMode = MachinePlaysBlack;
6184       StartClocks();
6185       SetGameInfo();
6186       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6187       DisplayTitle(buf);
6188       if (first.sendName) {
6189         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6190         SendToProgram(buf, &first);
6191       }
6192       StartClocks();
6193     }
6194     ModeHighlight();
6195   }
6196
6197   /* Relay move to ICS or chess engine */
6198   if (appData.icsActive) {
6199     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6200         gameMode == IcsExamining) {
6201       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6202         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6203         SendToICS("draw ");
6204         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6205       }
6206       // also send plain move, in case ICS does not understand atomic claims
6207       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6208       ics_user_moved = 1;
6209     }
6210   } else {
6211     if (first.sendTime && (gameMode == BeginningOfGame ||
6212                            gameMode == MachinePlaysWhite ||
6213                            gameMode == MachinePlaysBlack)) {
6214       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6215     }
6216     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6217          // [HGM] book: if program might be playing, let it use book
6218         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6219         first.maybeThinking = TRUE;
6220     } else SendMoveToProgram(forwardMostMove-1, &first);
6221     if (currentMove == cmailOldMove + 1) {
6222       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6223     }
6224   }
6225
6226   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6227
6228   switch (gameMode) {
6229   case EditGame:
6230     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6231     case MT_NONE:
6232     case MT_CHECK:
6233       break;
6234     case MT_CHECKMATE:
6235     case MT_STAINMATE:
6236       if (WhiteOnMove(currentMove)) {
6237         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6238       } else {
6239         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6240       }
6241       break;
6242     case MT_STALEMATE:
6243       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6244       break;
6245     }
6246     break;
6247
6248   case MachinePlaysBlack:
6249   case MachinePlaysWhite:
6250     /* disable certain menu options while machine is thinking */
6251     SetMachineThinkingEnables();
6252     break;
6253
6254   default:
6255     break;
6256   }
6257
6258   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6259
6260   if(bookHit) { // [HGM] book: simulate book reply
6261         static char bookMove[MSG_SIZ]; // a bit generous?
6262
6263         programStats.nodes = programStats.depth = programStats.time =
6264         programStats.score = programStats.got_only_move = 0;
6265         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6266
6267         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6268         strcat(bookMove, bookHit);
6269         HandleMachineMove(bookMove, &first);
6270   }
6271   return 1;
6272 }
6273
6274 void
6275 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6276      Board board;
6277      int flags;
6278      ChessMove kind;
6279      int rf, ff, rt, ft;
6280      VOIDSTAR closure;
6281 {
6282     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6283     Markers *m = (Markers *) closure;
6284     if(rf == fromY && ff == fromX)
6285         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6286                          || kind == WhiteCapturesEnPassant
6287                          || kind == BlackCapturesEnPassant);
6288     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6289 }
6290
6291 void
6292 MarkTargetSquares(int clear)
6293 {
6294   int x, y;
6295   if(!appData.markers || !appData.highlightDragging ||
6296      !appData.testLegality || gameMode == EditPosition) return;
6297   if(clear) {
6298     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6299   } else {
6300     int capt = 0;
6301     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6302     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6303       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6304       if(capt)
6305       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6306     }
6307   }
6308   DrawPosition(TRUE, NULL);
6309 }
6310
6311 int
6312 Explode(Board board, int fromX, int fromY, int toX, int toY)
6313 {
6314     if(gameInfo.variant == VariantAtomic &&
6315        (board[toY][toX] != EmptySquare ||                     // capture?
6316         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6317                          board[fromY][fromX] == BlackPawn   )
6318       )) {
6319         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6320         return TRUE;
6321     }
6322     return FALSE;
6323 }
6324
6325 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6326
6327 void LeftClick(ClickType clickType, int xPix, int yPix)
6328 {
6329     int x, y;
6330     Boolean saveAnimate;
6331     static int second = 0, promotionChoice = 0, dragging = 0;
6332     char promoChoice = NULLCHAR;
6333
6334     if(appData.seekGraph && appData.icsActive && loggedOn &&
6335         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6336         SeekGraphClick(clickType, xPix, yPix, 0);
6337         return;
6338     }
6339
6340     if (clickType == Press) ErrorPopDown();
6341     MarkTargetSquares(1);
6342
6343     x = EventToSquare(xPix, BOARD_WIDTH);
6344     y = EventToSquare(yPix, BOARD_HEIGHT);
6345     if (!flipView && y >= 0) {
6346         y = BOARD_HEIGHT - 1 - y;
6347     }
6348     if (flipView && x >= 0) {
6349         x = BOARD_WIDTH - 1 - x;
6350     }
6351
6352     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6353         if(clickType == Release) return; // ignore upclick of click-click destination
6354         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6355         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6356         if(gameInfo.holdingsWidth &&
6357                 (WhiteOnMove(currentMove)
6358                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6359                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6360             // click in right holdings, for determining promotion piece
6361             ChessSquare p = boards[currentMove][y][x];
6362             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6363             if(p != EmptySquare) {
6364                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6365                 fromX = fromY = -1;
6366                 return;
6367             }
6368         }
6369         DrawPosition(FALSE, boards[currentMove]);
6370         return;
6371     }
6372
6373     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6374     if(clickType == Press
6375             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6376               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6377               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6378         return;
6379
6380     autoQueen = appData.alwaysPromoteToQueen;
6381
6382     if (fromX == -1) {
6383       gatingPiece = EmptySquare;
6384       if (clickType != Press) {
6385         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6386             DragPieceEnd(xPix, yPix); dragging = 0;
6387             DrawPosition(FALSE, NULL);
6388         }
6389         return;
6390       }
6391       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6392             /* First square */
6393             if (OKToStartUserMove(x, y)) {
6394                 fromX = x;
6395                 fromY = y;
6396                 second = 0;
6397                 MarkTargetSquares(0);
6398                 DragPieceBegin(xPix, yPix); dragging = 1;
6399                 if (appData.highlightDragging) {
6400                     SetHighlights(x, y, -1, -1);
6401                 }
6402             }
6403             return;
6404         }
6405     }
6406
6407     /* fromX != -1 */
6408     if (clickType == Press && gameMode != EditPosition) {
6409         ChessSquare fromP;
6410         ChessSquare toP;
6411         int frc;
6412
6413         // ignore off-board to clicks
6414         if(y < 0 || x < 0) return;
6415
6416         /* Check if clicking again on the same color piece */
6417         fromP = boards[currentMove][fromY][fromX];
6418         toP = boards[currentMove][y][x];
6419         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6420         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6421              WhitePawn <= toP && toP <= WhiteKing &&
6422              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6423              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6424             (BlackPawn <= fromP && fromP <= BlackKing &&
6425              BlackPawn <= toP && toP <= BlackKing &&
6426              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6427              !(fromP == BlackKing && toP == BlackRook && frc))) {
6428             /* Clicked again on same color piece -- changed his mind */
6429             second = (x == fromX && y == fromY);
6430            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6431             if (appData.highlightDragging) {
6432                 SetHighlights(x, y, -1, -1);
6433             } else {
6434                 ClearHighlights();
6435             }
6436             if (OKToStartUserMove(x, y)) {
6437                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6438                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6439                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6440                  gatingPiece = boards[currentMove][fromY][fromX];
6441                 else gatingPiece = EmptySquare;
6442                 fromX = x;
6443                 fromY = y; dragging = 1;
6444                 MarkTargetSquares(0);
6445                 DragPieceBegin(xPix, yPix);
6446             }
6447            }
6448            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6449            second = FALSE; 
6450         }
6451         // ignore clicks on holdings
6452         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6453     }
6454
6455     if (clickType == Release && x == fromX && y == fromY) {
6456         DragPieceEnd(xPix, yPix); dragging = 0;
6457         if (appData.animateDragging) {
6458             /* Undo animation damage if any */
6459             DrawPosition(FALSE, NULL);
6460         }
6461         if (second) {
6462             /* Second up/down in same square; just abort move */
6463             second = 0;
6464             fromX = fromY = -1;
6465             gatingPiece = EmptySquare;
6466             ClearHighlights();
6467             gotPremove = 0;
6468             ClearPremoveHighlights();
6469         } else {
6470             /* First upclick in same square; start click-click mode */
6471             SetHighlights(x, y, -1, -1);
6472         }
6473         return;
6474     }
6475
6476     /* we now have a different from- and (possibly off-board) to-square */
6477     /* Completed move */
6478     toX = x;
6479     toY = y;
6480     saveAnimate = appData.animate;
6481     if (clickType == Press) {
6482         /* Finish clickclick move */
6483         if (appData.animate || appData.highlightLastMove) {
6484             SetHighlights(fromX, fromY, toX, toY);
6485         } else {
6486             ClearHighlights();
6487         }
6488     } else {
6489         /* Finish drag move */
6490         if (appData.highlightLastMove) {
6491             SetHighlights(fromX, fromY, toX, toY);
6492         } else {
6493             ClearHighlights();
6494         }
6495         DragPieceEnd(xPix, yPix); dragging = 0;
6496         /* Don't animate move and drag both */
6497         appData.animate = FALSE;
6498     }
6499
6500     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6501     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6502         ChessSquare piece = boards[currentMove][fromY][fromX];
6503         if(gameMode == EditPosition && piece != EmptySquare &&
6504            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6505             int n;
6506
6507             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6508                 n = PieceToNumber(piece - (int)BlackPawn);
6509                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6510                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6511                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6512             } else
6513             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6514                 n = PieceToNumber(piece);
6515                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6516                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6517                 boards[currentMove][n][BOARD_WIDTH-2]++;
6518             }
6519             boards[currentMove][fromY][fromX] = EmptySquare;
6520         }
6521         ClearHighlights();
6522         fromX = fromY = -1;
6523         DrawPosition(TRUE, boards[currentMove]);
6524         return;
6525     }
6526
6527     // off-board moves should not be highlighted
6528     if(x < 0 || y < 0) ClearHighlights();
6529
6530     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6531
6532     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6533         SetHighlights(fromX, fromY, toX, toY);
6534         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6535             // [HGM] super: promotion to captured piece selected from holdings
6536             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6537             promotionChoice = TRUE;
6538             // kludge follows to temporarily execute move on display, without promoting yet
6539             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6540             boards[currentMove][toY][toX] = p;
6541             DrawPosition(FALSE, boards[currentMove]);
6542             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6543             boards[currentMove][toY][toX] = q;
6544             DisplayMessage("Click in holdings to choose piece", "");
6545             return;
6546         }
6547         PromotionPopUp();
6548     } else {
6549         int oldMove = currentMove;
6550         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6551         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6552         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6553         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6554            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6555             DrawPosition(TRUE, boards[currentMove]);
6556         fromX = fromY = -1;
6557     }
6558     appData.animate = saveAnimate;
6559     if (appData.animate || appData.animateDragging) {
6560         /* Undo animation damage if needed */
6561         DrawPosition(FALSE, NULL);
6562     }
6563 }
6564
6565 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6566 {   // front-end-free part taken out of PieceMenuPopup
6567     int whichMenu; int xSqr, ySqr;
6568
6569     if(seekGraphUp) { // [HGM] seekgraph
6570         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6571         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6572         return -2;
6573     }
6574
6575     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6576          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6577         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6578         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6579         if(action == Press)   {
6580             originalFlip = flipView;
6581             flipView = !flipView; // temporarily flip board to see game from partners perspective
6582             DrawPosition(TRUE, partnerBoard);
6583             DisplayMessage(partnerStatus, "");
6584             partnerUp = TRUE;
6585         } else if(action == Release) {
6586             flipView = originalFlip;
6587             DrawPosition(TRUE, boards[currentMove]);
6588             partnerUp = FALSE;
6589         }
6590         return -2;
6591     }
6592
6593     xSqr = EventToSquare(x, BOARD_WIDTH);
6594     ySqr = EventToSquare(y, BOARD_HEIGHT);
6595     if (action == Release) UnLoadPV(); // [HGM] pv
6596     if (action != Press) return -2; // return code to be ignored
6597     switch (gameMode) {
6598       case IcsExamining:
6599         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6600       case EditPosition:
6601         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6602         if (xSqr < 0 || ySqr < 0) return -1;\r
6603         whichMenu = 0; // edit-position menu
6604         break;
6605       case IcsObserving:
6606         if(!appData.icsEngineAnalyze) return -1;
6607       case IcsPlayingWhite:
6608       case IcsPlayingBlack:
6609         if(!appData.zippyPlay) goto noZip;
6610       case AnalyzeMode:
6611       case AnalyzeFile:
6612       case MachinePlaysWhite:
6613       case MachinePlaysBlack:
6614       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6615         if (!appData.dropMenu) {
6616           LoadPV(x, y);
6617           return 2; // flag front-end to grab mouse events
6618         }
6619         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6620            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6621       case EditGame:
6622       noZip:
6623         if (xSqr < 0 || ySqr < 0) return -1;
6624         if (!appData.dropMenu || appData.testLegality &&
6625             gameInfo.variant != VariantBughouse &&
6626             gameInfo.variant != VariantCrazyhouse) return -1;
6627         whichMenu = 1; // drop menu
6628         break;
6629       default:
6630         return -1;
6631     }
6632
6633     if (((*fromX = xSqr) < 0) ||
6634         ((*fromY = ySqr) < 0)) {
6635         *fromX = *fromY = -1;
6636         return -1;
6637     }
6638     if (flipView)
6639       *fromX = BOARD_WIDTH - 1 - *fromX;
6640     else
6641       *fromY = BOARD_HEIGHT - 1 - *fromY;
6642
6643     return whichMenu;
6644 }
6645
6646 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6647 {
6648 //    char * hint = lastHint;
6649     FrontEndProgramStats stats;
6650
6651     stats.which = cps == &first ? 0 : 1;
6652     stats.depth = cpstats->depth;
6653     stats.nodes = cpstats->nodes;
6654     stats.score = cpstats->score;
6655     stats.time = cpstats->time;
6656     stats.pv = cpstats->movelist;
6657     stats.hint = lastHint;
6658     stats.an_move_index = 0;
6659     stats.an_move_count = 0;
6660
6661     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6662         stats.hint = cpstats->move_name;
6663         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6664         stats.an_move_count = cpstats->nr_moves;
6665     }
6666
6667     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
6668
6669     SetProgramStats( &stats );
6670 }
6671
6672 void
6673 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6674 {       // count all piece types
6675         int p, f, r;
6676         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6677         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6678         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6679                 p = board[r][f];
6680                 pCnt[p]++;
6681                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6682                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6683                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6684                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6685                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6686                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6687         }
6688 }
6689
6690 int
6691 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6692 {
6693         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6694         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6695
6696         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6697         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6698         if(myPawns == 2 && nMine == 3) // KPP
6699             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6700         if(myPawns == 1 && nMine == 2) // KP
6701             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6702         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6703             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6704         if(myPawns) return FALSE;
6705         if(pCnt[WhiteRook+side])
6706             return pCnt[BlackRook-side] ||
6707                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6708                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6709                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6710         if(pCnt[WhiteCannon+side]) {
6711             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6712             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6713         }
6714         if(pCnt[WhiteKnight+side])
6715             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6716         return FALSE;
6717 }
6718
6719 int
6720 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6721 {
6722         VariantClass v = gameInfo.variant;
6723
6724         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6725         if(v == VariantShatranj) return TRUE; // always winnable through baring
6726         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6727         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6728
6729         if(v == VariantXiangqi) {
6730                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6731
6732                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6733                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6734                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6735                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6736                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6737                 if(stale) // we have at least one last-rank P plus perhaps C
6738                     return majors // KPKX
6739                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6740                 else // KCA*E*
6741                     return pCnt[WhiteFerz+side] // KCAK
6742                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6743                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6744                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6745
6746         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6747                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6748
6749                 if(nMine == 1) return FALSE; // bare King
6750                 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
6751                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6752                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6753                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6754                 if(pCnt[WhiteKnight+side])
6755                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6756                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6757                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6758                 if(nBishops)
6759                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6760                 if(pCnt[WhiteAlfil+side])
6761                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6762                 if(pCnt[WhiteWazir+side])
6763                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6764         }
6765
6766         return TRUE;
6767 }
6768
6769 int
6770 Adjudicate(ChessProgramState *cps)
6771 {       // [HGM] some adjudications useful with buggy engines
6772         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6773         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6774         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6775         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6776         int k, count = 0; static int bare = 1;
6777         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6778         Boolean canAdjudicate = !appData.icsActive;
6779
6780         // most tests only when we understand the game, i.e. legality-checking on
6781             if( appData.testLegality )
6782             {   /* [HGM] Some more adjudications for obstinate engines */
6783                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6784                 static int moveCount = 6;
6785                 ChessMove result;
6786                 char *reason = NULL;
6787
6788                 /* Count what is on board. */
6789                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6790
6791                 /* Some material-based adjudications that have to be made before stalemate test */
6792                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6793                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6794                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6795                      if(canAdjudicate && appData.checkMates) {
6796                          if(engineOpponent)
6797                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6798                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6799                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6800                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6801                          return 1;
6802                      }
6803                 }
6804
6805                 /* Bare King in Shatranj (loses) or Losers (wins) */
6806                 if( nrW == 1 || nrB == 1) {
6807                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6808                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6809                      if(canAdjudicate && appData.checkMates) {
6810                          if(engineOpponent)
6811                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6812                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6813                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6814                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6815                          return 1;
6816                      }
6817                   } else
6818                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6819                   {    /* bare King */
6820                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6821                         if(canAdjudicate && appData.checkMates) {
6822                             /* but only adjudicate if adjudication enabled */
6823                             if(engineOpponent)
6824                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6825                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6826                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6827                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6828                             return 1;
6829                         }
6830                   }
6831                 } else bare = 1;
6832
6833
6834             // don't wait for engine to announce game end if we can judge ourselves
6835             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6836               case MT_CHECK:
6837                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6838                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6839                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6840                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6841                             checkCnt++;
6842                         if(checkCnt >= 2) {
6843                             reason = "Xboard adjudication: 3rd check";
6844                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6845                             break;
6846                         }
6847                     }
6848                 }
6849               case MT_NONE:
6850               default:
6851                 break;
6852               case MT_STALEMATE:
6853               case MT_STAINMATE:
6854                 reason = "Xboard adjudication: Stalemate";
6855                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6856                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6857                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6858                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6859                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6860                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6861                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6862                                                                         EP_CHECKMATE : EP_WINS);
6863                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6864                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6865                 }
6866                 break;
6867               case MT_CHECKMATE:
6868                 reason = "Xboard adjudication: Checkmate";
6869                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6870                 break;
6871             }
6872
6873                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6874                     case EP_STALEMATE:
6875                         result = GameIsDrawn; break;
6876                     case EP_CHECKMATE:
6877                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6878                     case EP_WINS:
6879                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6880                     default:
6881                         result = EndOfFile;
6882                 }
6883                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6884                     if(engineOpponent)
6885                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6886                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887                     GameEnds( result, reason, GE_XBOARD );
6888                     return 1;
6889                 }
6890
6891                 /* Next absolutely insufficient mating material. */
6892                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6893                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6894                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6895
6896                      /* always flag draws, for judging claims */
6897                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6898
6899                      if(canAdjudicate && appData.materialDraws) {
6900                          /* but only adjudicate them if adjudication enabled */
6901                          if(engineOpponent) {
6902                            SendToProgram("force\n", engineOpponent); // suppress reply
6903                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6904                          }
6905                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6906                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6907                          return 1;
6908                      }
6909                 }
6910
6911                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6912                 if(gameInfo.variant == VariantXiangqi ?
6913                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6914                  : nrW + nrB == 4 &&
6915                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6916                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6917                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6918                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6919                    ) ) {
6920                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6921                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6922                           if(engineOpponent) {
6923                             SendToProgram("force\n", engineOpponent); // suppress reply
6924                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6925                           }
6926                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6927                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6928                           return 1;
6929                      }
6930                 } else moveCount = 6;
6931             }
6932         if (appData.debugMode) { int i;
6933             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6934                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6935                     appData.drawRepeats);
6936             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6937               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6938
6939         }
6940
6941         // Repetition draws and 50-move rule can be applied independently of legality testing
6942
6943                 /* Check for rep-draws */
6944                 count = 0;
6945                 for(k = forwardMostMove-2;
6946                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6947                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6948                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6949                     k-=2)
6950                 {   int rights=0;
6951                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6952                         /* compare castling rights */
6953                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6954                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6955                                 rights++; /* King lost rights, while rook still had them */
6956                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6957                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6958                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6959                                    rights++; /* but at least one rook lost them */
6960                         }
6961                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6962                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6963                                 rights++;
6964                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6965                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6966                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6967                                    rights++;
6968                         }
6969                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6970                             && appData.drawRepeats > 1) {
6971                              /* adjudicate after user-specified nr of repeats */
6972                              int result = GameIsDrawn;
6973                              char *details = "XBoard adjudication: repetition draw";
6974                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6975                                 // [HGM] xiangqi: check for forbidden perpetuals
6976                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6977                                 for(m=forwardMostMove; m>k; m-=2) {
6978                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6979                                         ourPerpetual = 0; // the current mover did not always check
6980                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6981                                         hisPerpetual = 0; // the opponent did not always check
6982                                 }
6983                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6984                                                                         ourPerpetual, hisPerpetual);
6985                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6986                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6987                                     details = "Xboard adjudication: perpetual checking";
6988                                 } else
6989                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6990                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6991                                 } else
6992                                 // Now check for perpetual chases
6993                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6994                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6995                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6996                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6997                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6998                                         details = "Xboard adjudication: perpetual chasing";
6999                                     } else
7000                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7001                                         break; // Abort repetition-checking loop.
7002                                 }
7003                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7004                              }
7005                              if(engineOpponent) {
7006                                SendToProgram("force\n", engineOpponent); // suppress reply
7007                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7008                              }
7009                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7010                              GameEnds( result, details, GE_XBOARD );
7011                              return 1;
7012                         }
7013                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7014                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7015                     }
7016                 }
7017
7018                 /* Now we test for 50-move draws. Determine ply count */
7019                 count = forwardMostMove;
7020                 /* look for last irreversble move */
7021                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7022                     count--;
7023                 /* if we hit starting position, add initial plies */
7024                 if( count == backwardMostMove )
7025                     count -= initialRulePlies;
7026                 count = forwardMostMove - count;
7027                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7028                         // adjust reversible move counter for checks in Xiangqi
7029                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7030                         if(i < backwardMostMove) i = backwardMostMove;
7031                         while(i <= forwardMostMove) {
7032                                 lastCheck = inCheck; // check evasion does not count
7033                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7034                                 if(inCheck || lastCheck) count--; // check does not count
7035                                 i++;
7036                         }
7037                 }
7038                 if( count >= 100)
7039                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7040                          /* this is used to judge if draw claims are legal */
7041                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7042                          if(engineOpponent) {
7043                            SendToProgram("force\n", engineOpponent); // suppress reply
7044                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7045                          }
7046                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7047                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7048                          return 1;
7049                 }
7050
7051                 /* if draw offer is pending, treat it as a draw claim
7052                  * when draw condition present, to allow engines a way to
7053                  * claim draws before making their move to avoid a race
7054                  * condition occurring after their move
7055                  */
7056                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7057                          char *p = NULL;
7058                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7059                              p = "Draw claim: 50-move rule";
7060                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7061                              p = "Draw claim: 3-fold repetition";
7062                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7063                              p = "Draw claim: insufficient mating material";
7064                          if( p != NULL && canAdjudicate) {
7065                              if(engineOpponent) {
7066                                SendToProgram("force\n", engineOpponent); // suppress reply
7067                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7068                              }
7069                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7070                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7071                              return 1;
7072                          }
7073                 }
7074
7075                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7076                     if(engineOpponent) {
7077                       SendToProgram("force\n", engineOpponent); // suppress reply
7078                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7079                     }
7080                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7081                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7082                     return 1;
7083                 }
7084         return 0;
7085 }
7086
7087 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7088 {   // [HGM] book: this routine intercepts moves to simulate book replies
7089     char *bookHit = NULL;
7090
7091     //first determine if the incoming move brings opponent into his book
7092     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7093         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7094     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7095     if(bookHit != NULL && !cps->bookSuspend) {
7096         // make sure opponent is not going to reply after receiving move to book position
7097         SendToProgram("force\n", cps);
7098         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7099     }
7100     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7101     // now arrange restart after book miss
7102     if(bookHit) {
7103         // after a book hit we never send 'go', and the code after the call to this routine
7104         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7105         char buf[MSG_SIZ];
7106         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7107         SendToProgram(buf, cps);
7108         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7109     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7110         SendToProgram("go\n", cps);
7111         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7112     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7113         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7114             SendToProgram("go\n", cps);
7115         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7116     }
7117     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7118 }
7119
7120 char *savedMessage;
7121 ChessProgramState *savedState;
7122 void DeferredBookMove(void)
7123 {
7124         if(savedState->lastPing != savedState->lastPong)
7125                     ScheduleDelayedEvent(DeferredBookMove, 10);
7126         else
7127         HandleMachineMove(savedMessage, savedState);
7128 }
7129
7130 void
7131 HandleMachineMove(message, cps)
7132      char *message;
7133      ChessProgramState *cps;
7134 {
7135     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7136     char realname[MSG_SIZ];
7137     int fromX, fromY, toX, toY;
7138     ChessMove moveType;
7139     char promoChar;
7140     char *p;
7141     int machineWhite;
7142     char *bookHit;
7143
7144     cps->userError = 0;
7145
7146 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7147     /*
7148      * Kludge to ignore BEL characters
7149      */
7150     while (*message == '\007') message++;
7151
7152     /*
7153      * [HGM] engine debug message: ignore lines starting with '#' character
7154      */
7155     if(cps->debug && *message == '#') return;
7156
7157     /*
7158      * Look for book output
7159      */
7160     if (cps == &first && bookRequested) {
7161         if (message[0] == '\t' || message[0] == ' ') {
7162             /* Part of the book output is here; append it */
7163             strcat(bookOutput, message);
7164             strcat(bookOutput, "  \n");
7165             return;
7166         } else if (bookOutput[0] != NULLCHAR) {
7167             /* All of book output has arrived; display it */
7168             char *p = bookOutput;
7169             while (*p != NULLCHAR) {
7170                 if (*p == '\t') *p = ' ';
7171                 p++;
7172             }
7173             DisplayInformation(bookOutput);
7174             bookRequested = FALSE;
7175             /* Fall through to parse the current output */
7176         }
7177     }
7178
7179     /*
7180      * Look for machine move.
7181      */
7182     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7183         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7184     {
7185         /* This method is only useful on engines that support ping */
7186         if (cps->lastPing != cps->lastPong) {
7187           if (gameMode == BeginningOfGame) {
7188             /* Extra move from before last new; ignore */
7189             if (appData.debugMode) {
7190                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7191             }
7192           } else {
7193             if (appData.debugMode) {
7194                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7195                         cps->which, gameMode);
7196             }
7197
7198             SendToProgram("undo\n", cps);
7199           }
7200           return;
7201         }
7202
7203         switch (gameMode) {
7204           case BeginningOfGame:
7205             /* Extra move from before last reset; ignore */
7206             if (appData.debugMode) {
7207                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7208             }
7209             return;
7210
7211           case EndOfGame:
7212           case IcsIdle:
7213           default:
7214             /* Extra move after we tried to stop.  The mode test is
7215                not a reliable way of detecting this problem, but it's
7216                the best we can do on engines that don't support ping.
7217             */
7218             if (appData.debugMode) {
7219                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7220                         cps->which, gameMode);
7221             }
7222             SendToProgram("undo\n", cps);
7223             return;
7224
7225           case MachinePlaysWhite:
7226           case IcsPlayingWhite:
7227             machineWhite = TRUE;
7228             break;
7229
7230           case MachinePlaysBlack:
7231           case IcsPlayingBlack:
7232             machineWhite = FALSE;
7233             break;
7234
7235           case TwoMachinesPlay:
7236             machineWhite = (cps->twoMachinesColor[0] == 'w');
7237             break;
7238         }
7239         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7240             if (appData.debugMode) {
7241                 fprintf(debugFP,
7242                         "Ignoring move out of turn by %s, gameMode %d"
7243                         ", forwardMost %d\n",
7244                         cps->which, gameMode, forwardMostMove);
7245             }
7246             return;
7247         }
7248
7249     if (appData.debugMode) { int f = forwardMostMove;
7250         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7251                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7252                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7253     }
7254         if(cps->alphaRank) AlphaRank(machineMove, 4);
7255         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7256                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7257             /* Machine move could not be parsed; ignore it. */
7258           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7259                     machineMove, cps->which);
7260             DisplayError(buf1, 0);
7261             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7262                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7263             if (gameMode == TwoMachinesPlay) {
7264               GameEnds(machineWhite ? BlackWins : WhiteWins,
7265                        buf1, GE_XBOARD);
7266             }
7267             return;
7268         }
7269
7270         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7271         /* So we have to redo legality test with true e.p. status here,  */
7272         /* to make sure an illegal e.p. capture does not slip through,   */
7273         /* to cause a forfeit on a justified illegal-move complaint      */
7274         /* of the opponent.                                              */
7275         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7276            ChessMove moveType;
7277            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7278                              fromY, fromX, toY, toX, promoChar);
7279             if (appData.debugMode) {
7280                 int i;
7281                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7282                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7283                 fprintf(debugFP, "castling rights\n");
7284             }
7285             if(moveType == IllegalMove) {
7286               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7287                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7288                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7289                            buf1, GE_XBOARD);
7290                 return;
7291            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7292            /* [HGM] Kludge to handle engines that send FRC-style castling
7293               when they shouldn't (like TSCP-Gothic) */
7294            switch(moveType) {
7295              case WhiteASideCastleFR:
7296              case BlackASideCastleFR:
7297                toX+=2;
7298                currentMoveString[2]++;
7299                break;
7300              case WhiteHSideCastleFR:
7301              case BlackHSideCastleFR:
7302                toX--;
7303                currentMoveString[2]--;
7304                break;
7305              default: ; // nothing to do, but suppresses warning of pedantic compilers
7306            }
7307         }
7308         hintRequested = FALSE;
7309         lastHint[0] = NULLCHAR;
7310         bookRequested = FALSE;
7311         /* Program may be pondering now */
7312         cps->maybeThinking = TRUE;
7313         if (cps->sendTime == 2) cps->sendTime = 1;
7314         if (cps->offeredDraw) cps->offeredDraw--;
7315
7316         /* currentMoveString is set as a side-effect of ParseOneMove */
7317         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7318         strcat(machineMove, "\n");
7319         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7320
7321         /* [AS] Save move info*/
7322         pvInfoList[ forwardMostMove ].score = programStats.score;
7323         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7324         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7325
7326         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7327
7328         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7329         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7330             int count = 0;
7331
7332             while( count < adjudicateLossPlies ) {
7333                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7334
7335                 if( count & 1 ) {
7336                     score = -score; /* Flip score for winning side */
7337                 }
7338
7339                 if( score > adjudicateLossThreshold ) {
7340                     break;
7341                 }
7342
7343                 count++;
7344             }
7345
7346             if( count >= adjudicateLossPlies ) {
7347                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7348
7349                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7350                     "Xboard adjudication",
7351                     GE_XBOARD );
7352
7353                 return;
7354             }
7355         }
7356
7357         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7358
7359 #if ZIPPY
7360         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7361             first.initDone) {
7362           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7363                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7364                 SendToICS("draw ");
7365                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7366           }
7367           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7368           ics_user_moved = 1;
7369           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7370                 char buf[3*MSG_SIZ];
7371
7372                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7373                         programStats.score / 100.,
7374                         programStats.depth,
7375                         programStats.time / 100.,
7376                         (unsigned int)programStats.nodes,
7377                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7378                         programStats.movelist);
7379                 SendToICS(buf);
7380 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7381           }
7382         }
7383 #endif
7384
7385         /* [AS] Clear stats for next move */
7386         ClearProgramStats();
7387         thinkOutput[0] = NULLCHAR;
7388         hiddenThinkOutputState = 0;
7389
7390         bookHit = NULL;
7391         if (gameMode == TwoMachinesPlay) {
7392             /* [HGM] relaying draw offers moved to after reception of move */
7393             /* and interpreting offer as claim if it brings draw condition */
7394             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7395                 SendToProgram("draw\n", cps->other);
7396             }
7397             if (cps->other->sendTime) {
7398                 SendTimeRemaining(cps->other,
7399                                   cps->other->twoMachinesColor[0] == 'w');
7400             }
7401             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7402             if (firstMove && !bookHit) {
7403                 firstMove = FALSE;
7404                 if (cps->other->useColors) {
7405                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7406                 }
7407                 SendToProgram("go\n", cps->other);
7408             }
7409             cps->other->maybeThinking = TRUE;
7410         }
7411
7412         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7413
7414         if (!pausing && appData.ringBellAfterMoves) {
7415             RingBell();
7416         }
7417
7418         /*
7419          * Reenable menu items that were disabled while
7420          * machine was thinking
7421          */
7422         if (gameMode != TwoMachinesPlay)
7423             SetUserThinkingEnables();
7424
7425         // [HGM] book: after book hit opponent has received move and is now in force mode
7426         // force the book reply into it, and then fake that it outputted this move by jumping
7427         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7428         if(bookHit) {
7429                 static char bookMove[MSG_SIZ]; // a bit generous?
7430
7431                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7432                 strcat(bookMove, bookHit);
7433                 message = bookMove;
7434                 cps = cps->other;
7435                 programStats.nodes = programStats.depth = programStats.time =
7436                 programStats.score = programStats.got_only_move = 0;
7437                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7438
7439                 if(cps->lastPing != cps->lastPong) {
7440                     savedMessage = message; // args for deferred call
7441                     savedState = cps;
7442                     ScheduleDelayedEvent(DeferredBookMove, 10);
7443                     return;
7444                 }
7445                 goto FakeBookMove;
7446         }
7447
7448         return;
7449     }
7450
7451     /* Set special modes for chess engines.  Later something general
7452      *  could be added here; for now there is just one kludge feature,
7453      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7454      *  when "xboard" is given as an interactive command.
7455      */
7456     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7457         cps->useSigint = FALSE;
7458         cps->useSigterm = FALSE;
7459     }
7460     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7461       ParseFeatures(message+8, cps);
7462       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7463     }
7464
7465     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7466       int dummy, s=6; char buf[MSG_SIZ];
7467       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7468       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7469       ParseFEN(boards[0], &dummy, message+s);
7470       DrawPosition(TRUE, boards[0]);
7471       startedFromSetupPosition = TRUE;
7472       return;
7473     }
7474     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7475      * want this, I was asked to put it in, and obliged.
7476      */
7477     if (!strncmp(message, "setboard ", 9)) {
7478         Board initial_position;
7479
7480         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7481
7482         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7483             DisplayError(_("Bad FEN received from engine"), 0);
7484             return ;
7485         } else {
7486            Reset(TRUE, FALSE);
7487            CopyBoard(boards[0], initial_position);
7488            initialRulePlies = FENrulePlies;
7489            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7490            else gameMode = MachinePlaysBlack;
7491            DrawPosition(FALSE, boards[currentMove]);
7492         }
7493         return;
7494     }
7495
7496     /*
7497      * Look for communication commands
7498      */
7499     if (!strncmp(message, "telluser ", 9)) {
7500         if(message[9] == '\\' && message[10] == '\\')
7501             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7502         DisplayNote(message + 9);
7503         return;
7504     }
7505     if (!strncmp(message, "tellusererror ", 14)) {
7506         cps->userError = 1;
7507         if(message[14] == '\\' && message[15] == '\\')
7508             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7509         DisplayError(message + 14, 0);
7510         return;
7511     }
7512     if (!strncmp(message, "tellopponent ", 13)) {
7513       if (appData.icsActive) {
7514         if (loggedOn) {
7515           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7516           SendToICS(buf1);
7517         }
7518       } else {
7519         DisplayNote(message + 13);
7520       }
7521       return;
7522     }
7523     if (!strncmp(message, "tellothers ", 11)) {
7524       if (appData.icsActive) {
7525         if (loggedOn) {
7526           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7527           SendToICS(buf1);
7528         }
7529       }
7530       return;
7531     }
7532     if (!strncmp(message, "tellall ", 8)) {
7533       if (appData.icsActive) {
7534         if (loggedOn) {
7535           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7536           SendToICS(buf1);
7537         }
7538       } else {
7539         DisplayNote(message + 8);
7540       }
7541       return;
7542     }
7543     if (strncmp(message, "warning", 7) == 0) {
7544         /* Undocumented feature, use tellusererror in new code */
7545         DisplayError(message, 0);
7546         return;
7547     }
7548     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7549         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7550         strcat(realname, " query");
7551         AskQuestion(realname, buf2, buf1, cps->pr);
7552         return;
7553     }
7554     /* Commands from the engine directly to ICS.  We don't allow these to be
7555      *  sent until we are logged on. Crafty kibitzes have been known to
7556      *  interfere with the login process.
7557      */
7558     if (loggedOn) {
7559         if (!strncmp(message, "tellics ", 8)) {
7560             SendToICS(message + 8);
7561             SendToICS("\n");
7562             return;
7563         }
7564         if (!strncmp(message, "tellicsnoalias ", 15)) {
7565             SendToICS(ics_prefix);
7566             SendToICS(message + 15);
7567             SendToICS("\n");
7568             return;
7569         }
7570         /* The following are for backward compatibility only */
7571         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7572             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7573             SendToICS(ics_prefix);
7574             SendToICS(message);
7575             SendToICS("\n");
7576             return;
7577         }
7578     }
7579     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7580         return;
7581     }
7582     /*
7583      * If the move is illegal, cancel it and redraw the board.
7584      * Also deal with other error cases.  Matching is rather loose
7585      * here to accommodate engines written before the spec.
7586      */
7587     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7588         strncmp(message, "Error", 5) == 0) {
7589         if (StrStr(message, "name") ||
7590             StrStr(message, "rating") || StrStr(message, "?") ||
7591             StrStr(message, "result") || StrStr(message, "board") ||
7592             StrStr(message, "bk") || StrStr(message, "computer") ||
7593             StrStr(message, "variant") || StrStr(message, "hint") ||
7594             StrStr(message, "random") || StrStr(message, "depth") ||
7595             StrStr(message, "accepted")) {
7596             return;
7597         }
7598         if (StrStr(message, "protover")) {
7599           /* Program is responding to input, so it's apparently done
7600              initializing, and this error message indicates it is
7601              protocol version 1.  So we don't need to wait any longer
7602              for it to initialize and send feature commands. */
7603           FeatureDone(cps, 1);
7604           cps->protocolVersion = 1;
7605           return;
7606         }
7607         cps->maybeThinking = FALSE;
7608
7609         if (StrStr(message, "draw")) {
7610             /* Program doesn't have "draw" command */
7611             cps->sendDrawOffers = 0;
7612             return;
7613         }
7614         if (cps->sendTime != 1 &&
7615             (StrStr(message, "time") || StrStr(message, "otim"))) {
7616           /* Program apparently doesn't have "time" or "otim" command */
7617           cps->sendTime = 0;
7618           return;
7619         }
7620         if (StrStr(message, "analyze")) {
7621             cps->analysisSupport = FALSE;
7622             cps->analyzing = FALSE;
7623             Reset(FALSE, TRUE);
7624             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7625             DisplayError(buf2, 0);
7626             return;
7627         }
7628         if (StrStr(message, "(no matching move)st")) {
7629           /* Special kludge for GNU Chess 4 only */
7630           cps->stKludge = TRUE;
7631           SendTimeControl(cps, movesPerSession, timeControl,
7632                           timeIncrement, appData.searchDepth,
7633                           searchTime);
7634           return;
7635         }
7636         if (StrStr(message, "(no matching move)sd")) {
7637           /* Special kludge for GNU Chess 4 only */
7638           cps->sdKludge = TRUE;
7639           SendTimeControl(cps, movesPerSession, timeControl,
7640                           timeIncrement, appData.searchDepth,
7641                           searchTime);
7642           return;
7643         }
7644         if (!StrStr(message, "llegal")) {
7645             return;
7646         }
7647         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7648             gameMode == IcsIdle) return;
7649         if (forwardMostMove <= backwardMostMove) return;
7650         if (pausing) PauseEvent();
7651       if(appData.forceIllegal) {
7652             // [HGM] illegal: machine refused move; force position after move into it
7653           SendToProgram("force\n", cps);
7654           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7655                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7656                 // when black is to move, while there might be nothing on a2 or black
7657                 // might already have the move. So send the board as if white has the move.
7658                 // But first we must change the stm of the engine, as it refused the last move
7659                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7660                 if(WhiteOnMove(forwardMostMove)) {
7661                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7662                     SendBoard(cps, forwardMostMove); // kludgeless board
7663                 } else {
7664                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7665                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7666                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7667                 }
7668           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7669             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7670                  gameMode == TwoMachinesPlay)
7671               SendToProgram("go\n", cps);
7672             return;
7673       } else
7674         if (gameMode == PlayFromGameFile) {
7675             /* Stop reading this game file */
7676             gameMode = EditGame;
7677             ModeHighlight();
7678         }
7679         currentMove = forwardMostMove-1;
7680         DisplayMove(currentMove-1); /* before DisplayMoveError */
7681         SwitchClocks(forwardMostMove-1); // [HGM] race
7682         DisplayBothClocks();
7683         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7684                 parseList[currentMove], cps->which);
7685         DisplayMoveError(buf1);
7686         DrawPosition(FALSE, boards[currentMove]);
7687
7688         /* [HGM] illegal-move claim should forfeit game when Xboard */
7689         /* only passes fully legal moves                            */
7690         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7691             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7692                                 "False illegal-move claim", GE_XBOARD );
7693         }
7694         return;
7695     }
7696     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7697         /* Program has a broken "time" command that
7698            outputs a string not ending in newline.
7699            Don't use it. */
7700         cps->sendTime = 0;
7701     }
7702
7703     /*
7704      * If chess program startup fails, exit with an error message.
7705      * Attempts to recover here are futile.
7706      */
7707     if ((StrStr(message, "unknown host") != NULL)
7708         || (StrStr(message, "No remote directory") != NULL)
7709         || (StrStr(message, "not found") != NULL)
7710         || (StrStr(message, "No such file") != NULL)
7711         || (StrStr(message, "can't alloc") != NULL)
7712         || (StrStr(message, "Permission denied") != NULL)) {
7713
7714         cps->maybeThinking = FALSE;
7715         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7716                 cps->which, cps->program, cps->host, message);
7717         RemoveInputSource(cps->isr);
7718         DisplayFatalError(buf1, 0, 1);
7719         return;
7720     }
7721
7722     /*
7723      * Look for hint output
7724      */
7725     if (sscanf(message, "Hint: %s", buf1) == 1) {
7726         if (cps == &first && hintRequested) {
7727             hintRequested = FALSE;
7728             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7729                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7730                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7731                                     PosFlags(forwardMostMove),
7732                                     fromY, fromX, toY, toX, promoChar, buf1);
7733                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7734                 DisplayInformation(buf2);
7735             } else {
7736                 /* Hint move could not be parsed!? */
7737               snprintf(buf2, sizeof(buf2),
7738                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7739                         buf1, cps->which);
7740                 DisplayError(buf2, 0);
7741             }
7742         } else {
7743           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7744         }
7745         return;
7746     }
7747
7748     /*
7749      * Ignore other messages if game is not in progress
7750      */
7751     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7752         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7753
7754     /*
7755      * look for win, lose, draw, or draw offer
7756      */
7757     if (strncmp(message, "1-0", 3) == 0) {
7758         char *p, *q, *r = "";
7759         p = strchr(message, '{');
7760         if (p) {
7761             q = strchr(p, '}');
7762             if (q) {
7763                 *q = NULLCHAR;
7764                 r = p + 1;
7765             }
7766         }
7767         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7768         return;
7769     } else if (strncmp(message, "0-1", 3) == 0) {
7770         char *p, *q, *r = "";
7771         p = strchr(message, '{');
7772         if (p) {
7773             q = strchr(p, '}');
7774             if (q) {
7775                 *q = NULLCHAR;
7776                 r = p + 1;
7777             }
7778         }
7779         /* Kludge for Arasan 4.1 bug */
7780         if (strcmp(r, "Black resigns") == 0) {
7781             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7782             return;
7783         }
7784         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7785         return;
7786     } else if (strncmp(message, "1/2", 3) == 0) {
7787         char *p, *q, *r = "";
7788         p = strchr(message, '{');
7789         if (p) {
7790             q = strchr(p, '}');
7791             if (q) {
7792                 *q = NULLCHAR;
7793                 r = p + 1;
7794             }
7795         }
7796
7797         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7798         return;
7799
7800     } else if (strncmp(message, "White resign", 12) == 0) {
7801         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7802         return;
7803     } else if (strncmp(message, "Black resign", 12) == 0) {
7804         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7805         return;
7806     } else if (strncmp(message, "White matches", 13) == 0 ||
7807                strncmp(message, "Black matches", 13) == 0   ) {
7808         /* [HGM] ignore GNUShogi noises */
7809         return;
7810     } else if (strncmp(message, "White", 5) == 0 &&
7811                message[5] != '(' &&
7812                StrStr(message, "Black") == NULL) {
7813         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7814         return;
7815     } else if (strncmp(message, "Black", 5) == 0 &&
7816                message[5] != '(') {
7817         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7818         return;
7819     } else if (strcmp(message, "resign") == 0 ||
7820                strcmp(message, "computer resigns") == 0) {
7821         switch (gameMode) {
7822           case MachinePlaysBlack:
7823           case IcsPlayingBlack:
7824             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7825             break;
7826           case MachinePlaysWhite:
7827           case IcsPlayingWhite:
7828             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7829             break;
7830           case TwoMachinesPlay:
7831             if (cps->twoMachinesColor[0] == 'w')
7832               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7833             else
7834               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7835             break;
7836           default:
7837             /* can't happen */
7838             break;
7839         }
7840         return;
7841     } else if (strncmp(message, "opponent mates", 14) == 0) {
7842         switch (gameMode) {
7843           case MachinePlaysBlack:
7844           case IcsPlayingBlack:
7845             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7846             break;
7847           case MachinePlaysWhite:
7848           case IcsPlayingWhite:
7849             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7850             break;
7851           case TwoMachinesPlay:
7852             if (cps->twoMachinesColor[0] == 'w')
7853               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7854             else
7855               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7856             break;
7857           default:
7858             /* can't happen */
7859             break;
7860         }
7861         return;
7862     } else if (strncmp(message, "computer mates", 14) == 0) {
7863         switch (gameMode) {
7864           case MachinePlaysBlack:
7865           case IcsPlayingBlack:
7866             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7867             break;
7868           case MachinePlaysWhite:
7869           case IcsPlayingWhite:
7870             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7871             break;
7872           case TwoMachinesPlay:
7873             if (cps->twoMachinesColor[0] == 'w')
7874               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7875             else
7876               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7877             break;
7878           default:
7879             /* can't happen */
7880             break;
7881         }
7882         return;
7883     } else if (strncmp(message, "checkmate", 9) == 0) {
7884         if (WhiteOnMove(forwardMostMove)) {
7885             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7886         } else {
7887             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7888         }
7889         return;
7890     } else if (strstr(message, "Draw") != NULL ||
7891                strstr(message, "game is a draw") != NULL) {
7892         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7893         return;
7894     } else if (strstr(message, "offer") != NULL &&
7895                strstr(message, "draw") != NULL) {
7896 #if ZIPPY
7897         if (appData.zippyPlay && first.initDone) {
7898             /* Relay offer to ICS */
7899             SendToICS(ics_prefix);
7900             SendToICS("draw\n");
7901         }
7902 #endif
7903         cps->offeredDraw = 2; /* valid until this engine moves twice */
7904         if (gameMode == TwoMachinesPlay) {
7905             if (cps->other->offeredDraw) {
7906                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7907             /* [HGM] in two-machine mode we delay relaying draw offer      */
7908             /* until after we also have move, to see if it is really claim */
7909             }
7910         } else if (gameMode == MachinePlaysWhite ||
7911                    gameMode == MachinePlaysBlack) {
7912           if (userOfferedDraw) {
7913             DisplayInformation(_("Machine accepts your draw offer"));
7914             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7915           } else {
7916             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7917           }
7918         }
7919     }
7920
7921
7922     /*
7923      * Look for thinking output
7924      */
7925     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7926           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7927                                 ) {
7928         int plylev, mvleft, mvtot, curscore, time;
7929         char mvname[MOVE_LEN];
7930         u64 nodes; // [DM]
7931         char plyext;
7932         int ignore = FALSE;
7933         int prefixHint = FALSE;
7934         mvname[0] = NULLCHAR;
7935
7936         switch (gameMode) {
7937           case MachinePlaysBlack:
7938           case IcsPlayingBlack:
7939             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7940             break;
7941           case MachinePlaysWhite:
7942           case IcsPlayingWhite:
7943             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7944             break;
7945           case AnalyzeMode:
7946           case AnalyzeFile:
7947             break;
7948           case IcsObserving: /* [DM] icsEngineAnalyze */
7949             if (!appData.icsEngineAnalyze) ignore = TRUE;
7950             break;
7951           case TwoMachinesPlay:
7952             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7953                 ignore = TRUE;
7954             }
7955             break;
7956           default:
7957             ignore = TRUE;
7958             break;
7959         }
7960
7961         if (!ignore) {
7962             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7963             buf1[0] = NULLCHAR;
7964             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7965                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7966
7967                 if (plyext != ' ' && plyext != '\t') {
7968                     time *= 100;
7969                 }
7970
7971                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7972                 if( cps->scoreIsAbsolute &&
7973                     ( gameMode == MachinePlaysBlack ||
7974                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7975                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7976                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7977                      !WhiteOnMove(currentMove)
7978                     ) )
7979                 {
7980                     curscore = -curscore;
7981                 }
7982
7983
7984                 tempStats.depth = plylev;
7985                 tempStats.nodes = nodes;
7986                 tempStats.time = time;
7987                 tempStats.score = curscore;
7988                 tempStats.got_only_move = 0;
7989
7990                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7991                         int ticklen;
7992
7993                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7994                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7995                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7996                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7997                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7998                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7999                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8000                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8001                 }
8002
8003                 /* Buffer overflow protection */
8004                 if (buf1[0] != NULLCHAR) {
8005                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8006                         && appData.debugMode) {
8007                         fprintf(debugFP,
8008                                 "PV is too long; using the first %u bytes.\n",
8009                                 (unsigned) sizeof(tempStats.movelist) - 1);
8010                     }
8011
8012                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8013                 } else {
8014                     sprintf(tempStats.movelist, " no PV\n");
8015                 }
8016
8017                 if (tempStats.seen_stat) {
8018                     tempStats.ok_to_send = 1;
8019                 }
8020
8021                 if (strchr(tempStats.movelist, '(') != NULL) {
8022                     tempStats.line_is_book = 1;
8023                     tempStats.nr_moves = 0;
8024                     tempStats.moves_left = 0;
8025                 } else {
8026                     tempStats.line_is_book = 0;
8027                 }
8028
8029                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8030                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8031
8032                 SendProgramStatsToFrontend( cps, &tempStats );
8033
8034                 /*
8035                     [AS] Protect the thinkOutput buffer from overflow... this
8036                     is only useful if buf1 hasn't overflowed first!
8037                 */
8038                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8039                          plylev,
8040                          (gameMode == TwoMachinesPlay ?
8041                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8042                          ((double) curscore) / 100.0,
8043                          prefixHint ? lastHint : "",
8044                          prefixHint ? " " : "" );
8045
8046                 if( buf1[0] != NULLCHAR ) {
8047                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8048
8049                     if( strlen(buf1) > max_len ) {
8050                         if( appData.debugMode) {
8051                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8052                         }
8053                         buf1[max_len+1] = '\0';
8054                     }
8055
8056                     strcat( thinkOutput, buf1 );
8057                 }
8058
8059                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8060                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8061                     DisplayMove(currentMove - 1);
8062                 }
8063                 return;
8064
8065             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8066                 /* crafty (9.25+) says "(only move) <move>"
8067                  * if there is only 1 legal move
8068                  */
8069                 sscanf(p, "(only move) %s", buf1);
8070                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8071                 sprintf(programStats.movelist, "%s (only move)", buf1);
8072                 programStats.depth = 1;
8073                 programStats.nr_moves = 1;
8074                 programStats.moves_left = 1;
8075                 programStats.nodes = 1;
8076                 programStats.time = 1;
8077                 programStats.got_only_move = 1;
8078
8079                 /* Not really, but we also use this member to
8080                    mean "line isn't going to change" (Crafty
8081                    isn't searching, so stats won't change) */
8082                 programStats.line_is_book = 1;
8083
8084                 SendProgramStatsToFrontend( cps, &programStats );
8085
8086                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8087                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8088                     DisplayMove(currentMove - 1);
8089                 }
8090                 return;
8091             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8092                               &time, &nodes, &plylev, &mvleft,
8093                               &mvtot, mvname) >= 5) {
8094                 /* The stat01: line is from Crafty (9.29+) in response
8095                    to the "." command */
8096                 programStats.seen_stat = 1;
8097                 cps->maybeThinking = TRUE;
8098
8099                 if (programStats.got_only_move || !appData.periodicUpdates)
8100                   return;
8101
8102                 programStats.depth = plylev;
8103                 programStats.time = time;
8104                 programStats.nodes = nodes;
8105                 programStats.moves_left = mvleft;
8106                 programStats.nr_moves = mvtot;
8107                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8108                 programStats.ok_to_send = 1;
8109                 programStats.movelist[0] = '\0';
8110
8111                 SendProgramStatsToFrontend( cps, &programStats );
8112
8113                 return;
8114
8115             } else if (strncmp(message,"++",2) == 0) {
8116                 /* Crafty 9.29+ outputs this */
8117                 programStats.got_fail = 2;
8118                 return;
8119
8120             } else if (strncmp(message,"--",2) == 0) {
8121                 /* Crafty 9.29+ outputs this */
8122                 programStats.got_fail = 1;
8123                 return;
8124
8125             } else if (thinkOutput[0] != NULLCHAR &&
8126                        strncmp(message, "    ", 4) == 0) {
8127                 unsigned message_len;
8128
8129                 p = message;
8130                 while (*p && *p == ' ') p++;
8131
8132                 message_len = strlen( p );
8133
8134                 /* [AS] Avoid buffer overflow */
8135                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8136                     strcat(thinkOutput, " ");
8137                     strcat(thinkOutput, p);
8138                 }
8139
8140                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8141                     strcat(programStats.movelist, " ");
8142                     strcat(programStats.movelist, p);
8143                 }
8144
8145                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8146                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8147                     DisplayMove(currentMove - 1);
8148                 }
8149                 return;
8150             }
8151         }
8152         else {
8153             buf1[0] = NULLCHAR;
8154
8155             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8156                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8157             {
8158                 ChessProgramStats cpstats;
8159
8160                 if (plyext != ' ' && plyext != '\t') {
8161                     time *= 100;
8162                 }
8163
8164                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8165                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8166                     curscore = -curscore;
8167                 }
8168
8169                 cpstats.depth = plylev;
8170                 cpstats.nodes = nodes;
8171                 cpstats.time = time;
8172                 cpstats.score = curscore;
8173                 cpstats.got_only_move = 0;
8174                 cpstats.movelist[0] = '\0';
8175
8176                 if (buf1[0] != NULLCHAR) {
8177                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8178                 }
8179
8180                 cpstats.ok_to_send = 0;
8181                 cpstats.line_is_book = 0;
8182                 cpstats.nr_moves = 0;
8183                 cpstats.moves_left = 0;
8184
8185                 SendProgramStatsToFrontend( cps, &cpstats );
8186             }
8187         }
8188     }
8189 }
8190
8191
8192 /* Parse a game score from the character string "game", and
8193    record it as the history of the current game.  The game
8194    score is NOT assumed to start from the standard position.
8195    The display is not updated in any way.
8196    */
8197 void
8198 ParseGameHistory(game)
8199      char *game;
8200 {
8201     ChessMove moveType;
8202     int fromX, fromY, toX, toY, boardIndex;
8203     char promoChar;
8204     char *p, *q;
8205     char buf[MSG_SIZ];
8206
8207     if (appData.debugMode)
8208       fprintf(debugFP, "Parsing game history: %s\n", game);
8209
8210     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8211     gameInfo.site = StrSave(appData.icsHost);
8212     gameInfo.date = PGNDate();
8213     gameInfo.round = StrSave("-");
8214
8215     /* Parse out names of players */
8216     while (*game == ' ') game++;
8217     p = buf;
8218     while (*game != ' ') *p++ = *game++;
8219     *p = NULLCHAR;
8220     gameInfo.white = StrSave(buf);
8221     while (*game == ' ') game++;
8222     p = buf;
8223     while (*game != ' ' && *game != '\n') *p++ = *game++;
8224     *p = NULLCHAR;
8225     gameInfo.black = StrSave(buf);
8226
8227     /* Parse moves */
8228     boardIndex = blackPlaysFirst ? 1 : 0;
8229     yynewstr(game);
8230     for (;;) {
8231         yyboardindex = boardIndex;
8232         moveType = (ChessMove) Myylex();
8233         switch (moveType) {
8234           case IllegalMove:             /* maybe suicide chess, etc. */
8235   if (appData.debugMode) {
8236     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8237     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8238     setbuf(debugFP, NULL);
8239   }
8240           case WhitePromotion:
8241           case BlackPromotion:
8242           case WhiteNonPromotion:
8243           case BlackNonPromotion:
8244           case NormalMove:
8245           case WhiteCapturesEnPassant:
8246           case BlackCapturesEnPassant:
8247           case WhiteKingSideCastle:
8248           case WhiteQueenSideCastle:
8249           case BlackKingSideCastle:
8250           case BlackQueenSideCastle:
8251           case WhiteKingSideCastleWild:
8252           case WhiteQueenSideCastleWild:
8253           case BlackKingSideCastleWild:
8254           case BlackQueenSideCastleWild:
8255           /* PUSH Fabien */
8256           case WhiteHSideCastleFR:
8257           case WhiteASideCastleFR:
8258           case BlackHSideCastleFR:
8259           case BlackASideCastleFR:
8260           /* POP Fabien */
8261             fromX = currentMoveString[0] - AAA;
8262             fromY = currentMoveString[1] - ONE;
8263             toX = currentMoveString[2] - AAA;
8264             toY = currentMoveString[3] - ONE;
8265             promoChar = currentMoveString[4];
8266             break;
8267           case WhiteDrop:
8268           case BlackDrop:
8269             fromX = moveType == WhiteDrop ?
8270               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8271             (int) CharToPiece(ToLower(currentMoveString[0]));
8272             fromY = DROP_RANK;
8273             toX = currentMoveString[2] - AAA;
8274             toY = currentMoveString[3] - ONE;
8275             promoChar = NULLCHAR;
8276             break;
8277           case AmbiguousMove:
8278             /* bug? */
8279             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8280   if (appData.debugMode) {
8281     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8282     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8283     setbuf(debugFP, NULL);
8284   }
8285             DisplayError(buf, 0);
8286             return;
8287           case ImpossibleMove:
8288             /* bug? */
8289             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8290   if (appData.debugMode) {
8291     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8292     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8293     setbuf(debugFP, NULL);
8294   }
8295             DisplayError(buf, 0);
8296             return;
8297           case EndOfFile:
8298             if (boardIndex < backwardMostMove) {
8299                 /* Oops, gap.  How did that happen? */
8300                 DisplayError(_("Gap in move list"), 0);
8301                 return;
8302             }
8303             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8304             if (boardIndex > forwardMostMove) {
8305                 forwardMostMove = boardIndex;
8306             }
8307             return;
8308           case ElapsedTime:
8309             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8310                 strcat(parseList[boardIndex-1], " ");
8311                 strcat(parseList[boardIndex-1], yy_text);
8312             }
8313             continue;
8314           case Comment:
8315           case PGNTag:
8316           case NAG:
8317           default:
8318             /* ignore */
8319             continue;
8320           case WhiteWins:
8321           case BlackWins:
8322           case GameIsDrawn:
8323           case GameUnfinished:
8324             if (gameMode == IcsExamining) {
8325                 if (boardIndex < backwardMostMove) {
8326                     /* Oops, gap.  How did that happen? */
8327                     return;
8328                 }
8329                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8330                 return;
8331             }
8332             gameInfo.result = moveType;
8333             p = strchr(yy_text, '{');
8334             if (p == NULL) p = strchr(yy_text, '(');
8335             if (p == NULL) {
8336                 p = yy_text;
8337                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8338             } else {
8339                 q = strchr(p, *p == '{' ? '}' : ')');
8340                 if (q != NULL) *q = NULLCHAR;
8341                 p++;
8342             }
8343             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8344             gameInfo.resultDetails = StrSave(p);
8345             continue;
8346         }
8347         if (boardIndex >= forwardMostMove &&
8348             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8349             backwardMostMove = blackPlaysFirst ? 1 : 0;
8350             return;
8351         }
8352         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8353                                  fromY, fromX, toY, toX, promoChar,
8354                                  parseList[boardIndex]);
8355         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8356         /* currentMoveString is set as a side-effect of yylex */
8357         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8358         strcat(moveList[boardIndex], "\n");
8359         boardIndex++;
8360         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8361         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8362           case MT_NONE:
8363           case MT_STALEMATE:
8364           default:
8365             break;
8366           case MT_CHECK:
8367             if(gameInfo.variant != VariantShogi)
8368                 strcat(parseList[boardIndex - 1], "+");
8369             break;
8370           case MT_CHECKMATE:
8371           case MT_STAINMATE:
8372             strcat(parseList[boardIndex - 1], "#");
8373             break;
8374         }
8375     }
8376 }
8377
8378
8379 /* Apply a move to the given board  */
8380 void
8381 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8382      int fromX, fromY, toX, toY;
8383      int promoChar;
8384      Board board;
8385 {
8386   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8387   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8388
8389     /* [HGM] compute & store e.p. status and castling rights for new position */
8390     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8391
8392       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8393       oldEP = (signed char)board[EP_STATUS];
8394       board[EP_STATUS] = EP_NONE;
8395
8396       if( board[toY][toX] != EmptySquare )
8397            board[EP_STATUS] = EP_CAPTURE;
8398
8399   if (fromY == DROP_RANK) {
8400         /* must be first */
8401         piece = board[toY][toX] = (ChessSquare) fromX;
8402   } else {
8403       int i;
8404
8405       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8406            if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8407       } else
8408       if( board[fromY][fromX] == WhitePawn ) {
8409            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8410                board[EP_STATUS] = EP_PAWN_MOVE;
8411            if( toY-fromY==2) {
8412                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8413                         gameInfo.variant != VariantBerolina || toX < fromX)
8414                       board[EP_STATUS] = toX | berolina;
8415                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8416                         gameInfo.variant != VariantBerolina || toX > fromX)
8417                       board[EP_STATUS] = toX;
8418            }
8419       } else
8420       if( board[fromY][fromX] == BlackPawn ) {
8421            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8422                board[EP_STATUS] = EP_PAWN_MOVE;
8423            if( toY-fromY== -2) {
8424                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8425                         gameInfo.variant != VariantBerolina || toX < fromX)
8426                       board[EP_STATUS] = toX | berolina;
8427                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8428                         gameInfo.variant != VariantBerolina || toX > fromX)
8429                       board[EP_STATUS] = toX;
8430            }
8431        }
8432
8433        for(i=0; i<nrCastlingRights; i++) {
8434            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8435               board[CASTLING][i] == toX   && castlingRank[i] == toY
8436              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8437        }
8438
8439      if (fromX == toX && fromY == toY) return;
8440
8441      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8442      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8443      if(gameInfo.variant == VariantKnightmate)
8444          king += (int) WhiteUnicorn - (int) WhiteKing;
8445
8446     /* Code added by Tord: */
8447     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8448     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8449         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8450       board[fromY][fromX] = EmptySquare;
8451       board[toY][toX] = EmptySquare;
8452       if((toX > fromX) != (piece == WhiteRook)) {
8453         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8454       } else {
8455         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8456       }
8457     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8458                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8459       board[fromY][fromX] = EmptySquare;
8460       board[toY][toX] = EmptySquare;
8461       if((toX > fromX) != (piece == BlackRook)) {
8462         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8463       } else {
8464         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8465       }
8466     /* End of code added by Tord */
8467
8468     } else if (board[fromY][fromX] == king
8469         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8470         && toY == fromY && toX > fromX+1) {
8471         board[fromY][fromX] = EmptySquare;
8472         board[toY][toX] = king;
8473         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8474         board[fromY][BOARD_RGHT-1] = EmptySquare;
8475     } else if (board[fromY][fromX] == king
8476         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8477                && toY == fromY && toX < fromX-1) {
8478         board[fromY][fromX] = EmptySquare;
8479         board[toY][toX] = king;
8480         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8481         board[fromY][BOARD_LEFT] = EmptySquare;
8482     } else if (board[fromY][fromX] == WhitePawn
8483                && toY >= BOARD_HEIGHT-promoRank
8484                && gameInfo.variant != VariantXiangqi
8485                ) {
8486         /* white pawn promotion */
8487         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8488         if (board[toY][toX] == EmptySquare) {
8489             board[toY][toX] = WhiteQueen;
8490         }
8491         if(gameInfo.variant==VariantBughouse ||
8492            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8493             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8494         board[fromY][fromX] = EmptySquare;
8495     } else if ((fromY == BOARD_HEIGHT-4)
8496                && (toX != fromX)
8497                && gameInfo.variant != VariantXiangqi
8498                && gameInfo.variant != VariantBerolina
8499                && (board[fromY][fromX] == WhitePawn)
8500                && (board[toY][toX] == EmptySquare)) {
8501         board[fromY][fromX] = EmptySquare;
8502         board[toY][toX] = WhitePawn;
8503         captured = board[toY - 1][toX];
8504         board[toY - 1][toX] = EmptySquare;
8505     } else if ((fromY == BOARD_HEIGHT-4)
8506                && (toX == fromX)
8507                && gameInfo.variant == VariantBerolina
8508                && (board[fromY][fromX] == WhitePawn)
8509                && (board[toY][toX] == EmptySquare)) {
8510         board[fromY][fromX] = EmptySquare;
8511         board[toY][toX] = WhitePawn;
8512         if(oldEP & EP_BEROLIN_A) {
8513                 captured = board[fromY][fromX-1];
8514                 board[fromY][fromX-1] = EmptySquare;
8515         }else{  captured = board[fromY][fromX+1];
8516                 board[fromY][fromX+1] = EmptySquare;
8517         }
8518     } else if (board[fromY][fromX] == king
8519         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8520                && toY == fromY && toX > fromX+1) {
8521         board[fromY][fromX] = EmptySquare;
8522         board[toY][toX] = king;
8523         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8524         board[fromY][BOARD_RGHT-1] = EmptySquare;
8525     } else if (board[fromY][fromX] == king
8526         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8527                && toY == fromY && toX < fromX-1) {
8528         board[fromY][fromX] = EmptySquare;
8529         board[toY][toX] = king;
8530         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8531         board[fromY][BOARD_LEFT] = EmptySquare;
8532     } else if (fromY == 7 && fromX == 3
8533                && board[fromY][fromX] == BlackKing
8534                && toY == 7 && toX == 5) {
8535         board[fromY][fromX] = EmptySquare;
8536         board[toY][toX] = BlackKing;
8537         board[fromY][7] = EmptySquare;
8538         board[toY][4] = BlackRook;
8539     } else if (fromY == 7 && fromX == 3
8540                && board[fromY][fromX] == BlackKing
8541                && toY == 7 && toX == 1) {
8542         board[fromY][fromX] = EmptySquare;
8543         board[toY][toX] = BlackKing;
8544         board[fromY][0] = EmptySquare;
8545         board[toY][2] = BlackRook;
8546     } else if (board[fromY][fromX] == BlackPawn
8547                && toY < promoRank
8548                && gameInfo.variant != VariantXiangqi
8549                ) {
8550         /* black pawn promotion */
8551         board[toY][toX] = CharToPiece(ToLower(promoChar));
8552         if (board[toY][toX] == EmptySquare) {
8553             board[toY][toX] = BlackQueen;
8554         }
8555         if(gameInfo.variant==VariantBughouse ||
8556            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8557             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8558         board[fromY][fromX] = EmptySquare;
8559     } else if ((fromY == 3)
8560                && (toX != fromX)
8561                && gameInfo.variant != VariantXiangqi
8562                && gameInfo.variant != VariantBerolina
8563                && (board[fromY][fromX] == BlackPawn)
8564                && (board[toY][toX] == EmptySquare)) {
8565         board[fromY][fromX] = EmptySquare;
8566         board[toY][toX] = BlackPawn;
8567         captured = board[toY + 1][toX];
8568         board[toY + 1][toX] = EmptySquare;
8569     } else if ((fromY == 3)
8570                && (toX == fromX)
8571                && gameInfo.variant == VariantBerolina
8572                && (board[fromY][fromX] == BlackPawn)
8573                && (board[toY][toX] == EmptySquare)) {
8574         board[fromY][fromX] = EmptySquare;
8575         board[toY][toX] = BlackPawn;
8576         if(oldEP & EP_BEROLIN_A) {
8577                 captured = board[fromY][fromX-1];
8578                 board[fromY][fromX-1] = EmptySquare;
8579         }else{  captured = board[fromY][fromX+1];
8580                 board[fromY][fromX+1] = EmptySquare;
8581         }
8582     } else {
8583         board[toY][toX] = board[fromY][fromX];
8584         board[fromY][fromX] = EmptySquare;
8585     }
8586   }
8587
8588     if (gameInfo.holdingsWidth != 0) {
8589
8590       /* !!A lot more code needs to be written to support holdings  */
8591       /* [HGM] OK, so I have written it. Holdings are stored in the */
8592       /* penultimate board files, so they are automaticlly stored   */
8593       /* in the game history.                                       */
8594       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8595                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8596         /* Delete from holdings, by decreasing count */
8597         /* and erasing image if necessary            */
8598         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8599         if(p < (int) BlackPawn) { /* white drop */
8600              p -= (int)WhitePawn;
8601                  p = PieceToNumber((ChessSquare)p);
8602              if(p >= gameInfo.holdingsSize) p = 0;
8603              if(--board[p][BOARD_WIDTH-2] <= 0)
8604                   board[p][BOARD_WIDTH-1] = EmptySquare;
8605              if((int)board[p][BOARD_WIDTH-2] < 0)
8606                         board[p][BOARD_WIDTH-2] = 0;
8607         } else {                  /* black drop */
8608              p -= (int)BlackPawn;
8609                  p = PieceToNumber((ChessSquare)p);
8610              if(p >= gameInfo.holdingsSize) p = 0;
8611              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8612                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8613              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8614                         board[BOARD_HEIGHT-1-p][1] = 0;
8615         }
8616       }
8617       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8618           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8619         /* [HGM] holdings: Add to holdings, if holdings exist */
8620         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8621                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8622                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8623         }
8624         p = (int) captured;
8625         if (p >= (int) BlackPawn) {
8626           p -= (int)BlackPawn;
8627           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8628                   /* in Shogi restore piece to its original  first */
8629                   captured = (ChessSquare) (DEMOTED captured);
8630                   p = DEMOTED p;
8631           }
8632           p = PieceToNumber((ChessSquare)p);
8633           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8634           board[p][BOARD_WIDTH-2]++;
8635           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8636         } else {
8637           p -= (int)WhitePawn;
8638           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8639                   captured = (ChessSquare) (DEMOTED captured);
8640                   p = DEMOTED p;
8641           }
8642           p = PieceToNumber((ChessSquare)p);
8643           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8644           board[BOARD_HEIGHT-1-p][1]++;
8645           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8646         }
8647       }
8648     } else if (gameInfo.variant == VariantAtomic) {
8649       if (captured != EmptySquare) {
8650         int y, x;
8651         for (y = toY-1; y <= toY+1; y++) {
8652           for (x = toX-1; x <= toX+1; x++) {
8653             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8654                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8655               board[y][x] = EmptySquare;
8656             }
8657           }
8658         }
8659         board[toY][toX] = EmptySquare;
8660       }
8661     }
8662     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8663         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8664     } else
8665     if(promoChar == '+') {
8666         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8667         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8668     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8669         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8670     }
8671     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8672                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8673         // [HGM] superchess: take promotion piece out of holdings
8674         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8675         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8676             if(!--board[k][BOARD_WIDTH-2])
8677                 board[k][BOARD_WIDTH-1] = EmptySquare;
8678         } else {
8679             if(!--board[BOARD_HEIGHT-1-k][1])
8680                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8681         }
8682     }
8683
8684 }
8685
8686 /* Updates forwardMostMove */
8687 void
8688 MakeMove(fromX, fromY, toX, toY, promoChar)
8689      int fromX, fromY, toX, toY;
8690      int promoChar;
8691 {
8692 //    forwardMostMove++; // [HGM] bare: moved downstream
8693
8694     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8695         int timeLeft; static int lastLoadFlag=0; int king, piece;
8696         piece = boards[forwardMostMove][fromY][fromX];
8697         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8698         if(gameInfo.variant == VariantKnightmate)
8699             king += (int) WhiteUnicorn - (int) WhiteKing;
8700         if(forwardMostMove == 0) {
8701             if(blackPlaysFirst)
8702                 fprintf(serverMoves, "%s;", second.tidy);
8703             fprintf(serverMoves, "%s;", first.tidy);
8704             if(!blackPlaysFirst)
8705                 fprintf(serverMoves, "%s;", second.tidy);
8706         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8707         lastLoadFlag = loadFlag;
8708         // print base move
8709         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8710         // print castling suffix
8711         if( toY == fromY && piece == king ) {
8712             if(toX-fromX > 1)
8713                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8714             if(fromX-toX >1)
8715                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8716         }
8717         // e.p. suffix
8718         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8719              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8720              boards[forwardMostMove][toY][toX] == EmptySquare
8721              && fromX != toX && fromY != toY)
8722                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8723         // promotion suffix
8724         if(promoChar != NULLCHAR)
8725                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8726         if(!loadFlag) {
8727             fprintf(serverMoves, "/%d/%d",
8728                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8729             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8730             else                      timeLeft = blackTimeRemaining/1000;
8731             fprintf(serverMoves, "/%d", timeLeft);
8732         }
8733         fflush(serverMoves);
8734     }
8735
8736     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8737       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8738                         0, 1);
8739       return;
8740     }
8741     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8742     if (commentList[forwardMostMove+1] != NULL) {
8743         free(commentList[forwardMostMove+1]);
8744         commentList[forwardMostMove+1] = NULL;
8745     }
8746     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8747     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8748     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8749     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8750     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8751     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8752     gameInfo.result = GameUnfinished;
8753     if (gameInfo.resultDetails != NULL) {
8754         free(gameInfo.resultDetails);
8755         gameInfo.resultDetails = NULL;
8756     }
8757     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8758                               moveList[forwardMostMove - 1]);
8759     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8760                              PosFlags(forwardMostMove - 1),
8761                              fromY, fromX, toY, toX, promoChar,
8762                              parseList[forwardMostMove - 1]);
8763     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8764       case MT_NONE:
8765       case MT_STALEMATE:
8766       default:
8767         break;
8768       case MT_CHECK:
8769         if(gameInfo.variant != VariantShogi)
8770             strcat(parseList[forwardMostMove - 1], "+");
8771         break;
8772       case MT_CHECKMATE:
8773       case MT_STAINMATE:
8774         strcat(parseList[forwardMostMove - 1], "#");
8775         break;
8776     }
8777     if (appData.debugMode) {
8778         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8779     }
8780
8781 }
8782
8783 /* Updates currentMove if not pausing */
8784 void
8785 ShowMove(fromX, fromY, toX, toY)
8786 {
8787     int instant = (gameMode == PlayFromGameFile) ?
8788         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8789     if(appData.noGUI) return;
8790     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8791         if (!instant) {
8792             if (forwardMostMove == currentMove + 1) {
8793                 AnimateMove(boards[forwardMostMove - 1],
8794                             fromX, fromY, toX, toY);
8795             }
8796             if (appData.highlightLastMove) {
8797                 SetHighlights(fromX, fromY, toX, toY);
8798             }
8799         }
8800         currentMove = forwardMostMove;
8801     }
8802
8803     if (instant) return;
8804
8805     DisplayMove(currentMove - 1);
8806     DrawPosition(FALSE, boards[currentMove]);
8807     DisplayBothClocks();
8808     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8809 }
8810
8811 void SendEgtPath(ChessProgramState *cps)
8812 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8813         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8814
8815         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8816
8817         while(*p) {
8818             char c, *q = name+1, *r, *s;
8819
8820             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8821             while(*p && *p != ',') *q++ = *p++;
8822             *q++ = ':'; *q = 0;
8823             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8824                 strcmp(name, ",nalimov:") == 0 ) {
8825                 // take nalimov path from the menu-changeable option first, if it is defined
8826               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8827                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8828             } else
8829             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8830                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8831                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8832                 s = r = StrStr(s, ":") + 1; // beginning of path info
8833                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8834                 c = *r; *r = 0;             // temporarily null-terminate path info
8835                     *--q = 0;               // strip of trailig ':' from name
8836                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8837                 *r = c;
8838                 SendToProgram(buf,cps);     // send egtbpath command for this format
8839             }
8840             if(*p == ',') p++; // read away comma to position for next format name
8841         }
8842 }
8843
8844 void
8845 InitChessProgram(cps, setup)
8846      ChessProgramState *cps;
8847      int setup; /* [HGM] needed to setup FRC opening position */
8848 {
8849     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8850     if (appData.noChessProgram) return;
8851     hintRequested = FALSE;
8852     bookRequested = FALSE;
8853
8854     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8855     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8856     if(cps->memSize) { /* [HGM] memory */
8857       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8858         SendToProgram(buf, cps);
8859     }
8860     SendEgtPath(cps); /* [HGM] EGT */
8861     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8862       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8863         SendToProgram(buf, cps);
8864     }
8865
8866     SendToProgram(cps->initString, cps);
8867     if (gameInfo.variant != VariantNormal &&
8868         gameInfo.variant != VariantLoadable
8869         /* [HGM] also send variant if board size non-standard */
8870         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8871                                             ) {
8872       char *v = VariantName(gameInfo.variant);
8873       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8874         /* [HGM] in protocol 1 we have to assume all variants valid */
8875         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8876         DisplayFatalError(buf, 0, 1);
8877         return;
8878       }
8879
8880       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8881       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8882       if( gameInfo.variant == VariantXiangqi )
8883            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8884       if( gameInfo.variant == VariantShogi )
8885            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8886       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8887            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8888       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8889                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8890            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8891       if( gameInfo.variant == VariantCourier )
8892            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8893       if( gameInfo.variant == VariantSuper )
8894            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8895       if( gameInfo.variant == VariantGreat )
8896            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8897       if( gameInfo.variant == VariantSChess )
8898            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8899
8900       if(overruled) {
8901         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8902                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8903            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8904            if(StrStr(cps->variants, b) == NULL) {
8905                // specific sized variant not known, check if general sizing allowed
8906                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8907                    if(StrStr(cps->variants, "boardsize") == NULL) {
8908                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8909                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8910                        DisplayFatalError(buf, 0, 1);
8911                        return;
8912                    }
8913                    /* [HGM] here we really should compare with the maximum supported board size */
8914                }
8915            }
8916       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8917       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8918       SendToProgram(buf, cps);
8919     }
8920     currentlyInitializedVariant = gameInfo.variant;
8921
8922     /* [HGM] send opening position in FRC to first engine */
8923     if(setup) {
8924           SendToProgram("force\n", cps);
8925           SendBoard(cps, 0);
8926           /* engine is now in force mode! Set flag to wake it up after first move. */
8927           setboardSpoiledMachineBlack = 1;
8928     }
8929
8930     if (cps->sendICS) {
8931       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8932       SendToProgram(buf, cps);
8933     }
8934     cps->maybeThinking = FALSE;
8935     cps->offeredDraw = 0;
8936     if (!appData.icsActive) {
8937         SendTimeControl(cps, movesPerSession, timeControl,
8938                         timeIncrement, appData.searchDepth,
8939                         searchTime);
8940     }
8941     if (appData.showThinking
8942         // [HGM] thinking: four options require thinking output to be sent
8943         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8944                                 ) {
8945         SendToProgram("post\n", cps);
8946     }
8947     SendToProgram("hard\n", cps);
8948     if (!appData.ponderNextMove) {
8949         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8950            it without being sure what state we are in first.  "hard"
8951            is not a toggle, so that one is OK.
8952          */
8953         SendToProgram("easy\n", cps);
8954     }
8955     if (cps->usePing) {
8956       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8957       SendToProgram(buf, cps);
8958     }
8959     cps->initDone = TRUE;
8960 }
8961
8962
8963 void
8964 StartChessProgram(cps)
8965      ChessProgramState *cps;
8966 {
8967     char buf[MSG_SIZ];
8968     int err;
8969
8970     if (appData.noChessProgram) return;
8971     cps->initDone = FALSE;
8972
8973     if (strcmp(cps->host, "localhost") == 0) {
8974         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8975     } else if (*appData.remoteShell == NULLCHAR) {
8976         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8977     } else {
8978         if (*appData.remoteUser == NULLCHAR) {
8979           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8980                     cps->program);
8981         } else {
8982           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8983                     cps->host, appData.remoteUser, cps->program);
8984         }
8985         err = StartChildProcess(buf, "", &cps->pr);
8986     }
8987
8988     if (err != 0) {
8989       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8990         DisplayFatalError(buf, err, 1);
8991         cps->pr = NoProc;
8992         cps->isr = NULL;
8993         return;
8994     }
8995
8996     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8997     if (cps->protocolVersion > 1) {
8998       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8999       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9000       cps->comboCnt = 0;  //                and values of combo boxes
9001       SendToProgram(buf, cps);
9002     } else {
9003       SendToProgram("xboard\n", cps);
9004     }
9005 }
9006
9007
9008 void
9009 TwoMachinesEventIfReady P((void))
9010 {
9011   if (first.lastPing != first.lastPong) {
9012     DisplayMessage("", _("Waiting for first chess program"));
9013     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9014     return;
9015   }
9016   if (second.lastPing != second.lastPong) {
9017     DisplayMessage("", _("Waiting for second chess program"));
9018     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9019     return;
9020   }
9021   ThawUI();
9022   TwoMachinesEvent();
9023 }
9024
9025 void
9026 NextMatchGame P((void))
9027 {
9028     int index; /* [HGM] autoinc: step load index during match */
9029     Reset(FALSE, TRUE);
9030     if (*appData.loadGameFile != NULLCHAR) {
9031         index = appData.loadGameIndex;
9032         if(index < 0) { // [HGM] autoinc
9033             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9034             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9035         }
9036         LoadGameFromFile(appData.loadGameFile,
9037                          index,
9038                          appData.loadGameFile, FALSE);
9039     } else if (*appData.loadPositionFile != NULLCHAR) {
9040         index = appData.loadPositionIndex;
9041         if(index < 0) { // [HGM] autoinc
9042             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9043             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9044         }
9045         LoadPositionFromFile(appData.loadPositionFile,
9046                              index,
9047                              appData.loadPositionFile);
9048     }
9049     TwoMachinesEventIfReady();
9050 }
9051
9052 void UserAdjudicationEvent( int result )
9053 {
9054     ChessMove gameResult = GameIsDrawn;
9055
9056     if( result > 0 ) {
9057         gameResult = WhiteWins;
9058     }
9059     else if( result < 0 ) {
9060         gameResult = BlackWins;
9061     }
9062
9063     if( gameMode == TwoMachinesPlay ) {
9064         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9065     }
9066 }
9067
9068
9069 // [HGM] save: calculate checksum of game to make games easily identifiable
9070 int StringCheckSum(char *s)
9071 {
9072         int i = 0;
9073         if(s==NULL) return 0;
9074         while(*s) i = i*259 + *s++;
9075         return i;
9076 }
9077
9078 int GameCheckSum()
9079 {
9080         int i, sum=0;
9081         for(i=backwardMostMove; i<forwardMostMove; i++) {
9082                 sum += pvInfoList[i].depth;
9083                 sum += StringCheckSum(parseList[i]);
9084                 sum += StringCheckSum(commentList[i]);
9085                 sum *= 261;
9086         }
9087         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9088         return sum + StringCheckSum(commentList[i]);
9089 } // end of save patch
9090
9091 void
9092 GameEnds(result, resultDetails, whosays)
9093      ChessMove result;
9094      char *resultDetails;
9095      int whosays;
9096 {
9097     GameMode nextGameMode;
9098     int isIcsGame;
9099     char buf[MSG_SIZ], popupRequested = 0;
9100
9101     if(endingGame) return; /* [HGM] crash: forbid recursion */
9102     endingGame = 1;
9103     if(twoBoards) { // [HGM] dual: switch back to one board
9104         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9105         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9106     }
9107     if (appData.debugMode) {
9108       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9109               result, resultDetails ? resultDetails : "(null)", whosays);
9110     }
9111
9112     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9113
9114     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9115         /* If we are playing on ICS, the server decides when the
9116            game is over, but the engine can offer to draw, claim
9117            a draw, or resign.
9118          */
9119 #if ZIPPY
9120         if (appData.zippyPlay && first.initDone) {
9121             if (result == GameIsDrawn) {
9122                 /* In case draw still needs to be claimed */
9123                 SendToICS(ics_prefix);
9124                 SendToICS("draw\n");
9125             } else if (StrCaseStr(resultDetails, "resign")) {
9126                 SendToICS(ics_prefix);
9127                 SendToICS("resign\n");
9128             }
9129         }
9130 #endif
9131         endingGame = 0; /* [HGM] crash */
9132         return;
9133     }
9134
9135     /* If we're loading the game from a file, stop */
9136     if (whosays == GE_FILE) {
9137       (void) StopLoadGameTimer();
9138       gameFileFP = NULL;
9139     }
9140
9141     /* Cancel draw offers */
9142     first.offeredDraw = second.offeredDraw = 0;
9143
9144     /* If this is an ICS game, only ICS can really say it's done;
9145        if not, anyone can. */
9146     isIcsGame = (gameMode == IcsPlayingWhite ||
9147                  gameMode == IcsPlayingBlack ||
9148                  gameMode == IcsObserving    ||
9149                  gameMode == IcsExamining);
9150
9151     if (!isIcsGame || whosays == GE_ICS) {
9152         /* OK -- not an ICS game, or ICS said it was done */
9153         StopClocks();
9154         if (!isIcsGame && !appData.noChessProgram)
9155           SetUserThinkingEnables();
9156
9157         /* [HGM] if a machine claims the game end we verify this claim */
9158         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9159             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9160                 char claimer;
9161                 ChessMove trueResult = (ChessMove) -1;
9162
9163                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9164                                             first.twoMachinesColor[0] :
9165                                             second.twoMachinesColor[0] ;
9166
9167                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9168                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9169                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9170                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9171                 } else
9172                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9173                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9174                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9175                 } else
9176                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9177                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9178                 }
9179
9180                 // now verify win claims, but not in drop games, as we don't understand those yet
9181                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9182                                                  || gameInfo.variant == VariantGreat) &&
9183                     (result == WhiteWins && claimer == 'w' ||
9184                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9185                       if (appData.debugMode) {
9186                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9187                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9188                       }
9189                       if(result != trueResult) {
9190                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9191                               result = claimer == 'w' ? BlackWins : WhiteWins;
9192                               resultDetails = buf;
9193                       }
9194                 } else
9195                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9196                     && (forwardMostMove <= backwardMostMove ||
9197                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9198                         (claimer=='b')==(forwardMostMove&1))
9199                                                                                   ) {
9200                       /* [HGM] verify: draws that were not flagged are false claims */
9201                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9202                       result = claimer == 'w' ? BlackWins : WhiteWins;
9203                       resultDetails = buf;
9204                 }
9205                 /* (Claiming a loss is accepted no questions asked!) */
9206             }
9207             /* [HGM] bare: don't allow bare King to win */
9208             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9209                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9210                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9211                && result != GameIsDrawn)
9212             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9213                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9214                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9215                         if(p >= 0 && p <= (int)WhiteKing) k++;
9216                 }
9217                 if (appData.debugMode) {
9218                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9219                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9220                 }
9221                 if(k <= 1) {
9222                         result = GameIsDrawn;
9223                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9224                         resultDetails = buf;
9225                 }
9226             }
9227         }
9228
9229
9230         if(serverMoves != NULL && !loadFlag) { char c = '=';
9231             if(result==WhiteWins) c = '+';
9232             if(result==BlackWins) c = '-';
9233             if(resultDetails != NULL)
9234                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9235         }
9236         if (resultDetails != NULL) {
9237             gameInfo.result = result;
9238             gameInfo.resultDetails = StrSave(resultDetails);
9239
9240             /* display last move only if game was not loaded from file */
9241             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9242                 DisplayMove(currentMove - 1);
9243
9244             if (forwardMostMove != 0) {
9245                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9246                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9247                                                                 ) {
9248                     if (*appData.saveGameFile != NULLCHAR) {
9249                         SaveGameToFile(appData.saveGameFile, TRUE);
9250                     } else if (appData.autoSaveGames) {
9251                         AutoSaveGame();
9252                     }
9253                     if (*appData.savePositionFile != NULLCHAR) {
9254                         SavePositionToFile(appData.savePositionFile);
9255                     }
9256                 }
9257             }
9258
9259             /* Tell program how game ended in case it is learning */
9260             /* [HGM] Moved this to after saving the PGN, just in case */
9261             /* engine died and we got here through time loss. In that */
9262             /* case we will get a fatal error writing the pipe, which */
9263             /* would otherwise lose us the PGN.                       */
9264             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9265             /* output during GameEnds should never be fatal anymore   */
9266             if (gameMode == MachinePlaysWhite ||
9267                 gameMode == MachinePlaysBlack ||
9268                 gameMode == TwoMachinesPlay ||
9269                 gameMode == IcsPlayingWhite ||
9270                 gameMode == IcsPlayingBlack ||
9271                 gameMode == BeginningOfGame) {
9272                 char buf[MSG_SIZ];
9273                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9274                         resultDetails);
9275                 if (first.pr != NoProc) {
9276                     SendToProgram(buf, &first);
9277                 }
9278                 if (second.pr != NoProc &&
9279                     gameMode == TwoMachinesPlay) {
9280                     SendToProgram(buf, &second);
9281                 }
9282             }
9283         }
9284
9285         if (appData.icsActive) {
9286             if (appData.quietPlay &&
9287                 (gameMode == IcsPlayingWhite ||
9288                  gameMode == IcsPlayingBlack)) {
9289                 SendToICS(ics_prefix);
9290                 SendToICS("set shout 1\n");
9291             }
9292             nextGameMode = IcsIdle;
9293             ics_user_moved = FALSE;
9294             /* clean up premove.  It's ugly when the game has ended and the
9295              * premove highlights are still on the board.
9296              */
9297             if (gotPremove) {
9298               gotPremove = FALSE;
9299               ClearPremoveHighlights();
9300               DrawPosition(FALSE, boards[currentMove]);
9301             }
9302             if (whosays == GE_ICS) {
9303                 switch (result) {
9304                 case WhiteWins:
9305                     if (gameMode == IcsPlayingWhite)
9306                         PlayIcsWinSound();
9307                     else if(gameMode == IcsPlayingBlack)
9308                         PlayIcsLossSound();
9309                     break;
9310                 case BlackWins:
9311                     if (gameMode == IcsPlayingBlack)
9312                         PlayIcsWinSound();
9313                     else if(gameMode == IcsPlayingWhite)
9314                         PlayIcsLossSound();
9315                     break;
9316                 case GameIsDrawn:
9317                     PlayIcsDrawSound();
9318                     break;
9319                 default:
9320                     PlayIcsUnfinishedSound();
9321                 }
9322             }
9323         } else if (gameMode == EditGame ||
9324                    gameMode == PlayFromGameFile ||
9325                    gameMode == AnalyzeMode ||
9326                    gameMode == AnalyzeFile) {
9327             nextGameMode = gameMode;
9328         } else {
9329             nextGameMode = EndOfGame;
9330         }
9331         pausing = FALSE;
9332         ModeHighlight();
9333     } else {
9334         nextGameMode = gameMode;
9335     }
9336
9337     if (appData.noChessProgram) {
9338         gameMode = nextGameMode;
9339         ModeHighlight();
9340         endingGame = 0; /* [HGM] crash */
9341         return;
9342     }
9343
9344     if (first.reuse) {
9345         /* Put first chess program into idle state */
9346         if (first.pr != NoProc &&
9347             (gameMode == MachinePlaysWhite ||
9348              gameMode == MachinePlaysBlack ||
9349              gameMode == TwoMachinesPlay ||
9350              gameMode == IcsPlayingWhite ||
9351              gameMode == IcsPlayingBlack ||
9352              gameMode == BeginningOfGame)) {
9353             SendToProgram("force\n", &first);
9354             if (first.usePing) {
9355               char buf[MSG_SIZ];
9356               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9357               SendToProgram(buf, &first);
9358             }
9359         }
9360     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9361         /* Kill off first chess program */
9362         if (first.isr != NULL)
9363           RemoveInputSource(first.isr);
9364         first.isr = NULL;
9365
9366         if (first.pr != NoProc) {
9367             ExitAnalyzeMode();
9368             DoSleep( appData.delayBeforeQuit );
9369             SendToProgram("quit\n", &first);
9370             DoSleep( appData.delayAfterQuit );
9371             DestroyChildProcess(first.pr, first.useSigterm);
9372         }
9373         first.pr = NoProc;
9374     }
9375     if (second.reuse) {
9376         /* Put second chess program into idle state */
9377         if (second.pr != NoProc &&
9378             gameMode == TwoMachinesPlay) {
9379             SendToProgram("force\n", &second);
9380             if (second.usePing) {
9381               char buf[MSG_SIZ];
9382               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9383               SendToProgram(buf, &second);
9384             }
9385         }
9386     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9387         /* Kill off second chess program */
9388         if (second.isr != NULL)
9389           RemoveInputSource(second.isr);
9390         second.isr = NULL;
9391
9392         if (second.pr != NoProc) {
9393             DoSleep( appData.delayBeforeQuit );
9394             SendToProgram("quit\n", &second);
9395             DoSleep( appData.delayAfterQuit );
9396             DestroyChildProcess(second.pr, second.useSigterm);
9397         }
9398         second.pr = NoProc;
9399     }
9400
9401     if (matchMode && gameMode == TwoMachinesPlay) {
9402         switch (result) {
9403         case WhiteWins:
9404           if (first.twoMachinesColor[0] == 'w') {
9405             first.matchWins++;
9406           } else {
9407             second.matchWins++;
9408           }
9409           break;
9410         case BlackWins:
9411           if (first.twoMachinesColor[0] == 'b') {
9412             first.matchWins++;
9413           } else {
9414             second.matchWins++;
9415           }
9416           break;
9417         default:
9418           break;
9419         }
9420         if (matchGame < appData.matchGames) {
9421             char *tmp;
9422             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9423                 tmp = first.twoMachinesColor;
9424                 first.twoMachinesColor = second.twoMachinesColor;
9425                 second.twoMachinesColor = tmp;
9426             }
9427             gameMode = nextGameMode;
9428             matchGame++;
9429             if(appData.matchPause>10000 || appData.matchPause<10)
9430                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9431             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9432             endingGame = 0; /* [HGM] crash */
9433             return;
9434         } else {
9435             gameMode = nextGameMode;
9436             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9437                      first.tidy, second.tidy,
9438                      first.matchWins, second.matchWins,
9439                      appData.matchGames - (first.matchWins + second.matchWins));
9440             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9441         }
9442     }
9443     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9444         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9445       ExitAnalyzeMode();
9446     gameMode = nextGameMode;
9447     ModeHighlight();
9448     endingGame = 0;  /* [HGM] crash */
9449     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9450       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9451         matchMode = FALSE; appData.matchGames = matchGame = 0;
9452         DisplayNote(buf);
9453       }
9454     }
9455 }
9456
9457 /* Assumes program was just initialized (initString sent).
9458    Leaves program in force mode. */
9459 void
9460 FeedMovesToProgram(cps, upto)
9461      ChessProgramState *cps;
9462      int upto;
9463 {
9464     int i;
9465
9466     if (appData.debugMode)
9467       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9468               startedFromSetupPosition ? "position and " : "",
9469               backwardMostMove, upto, cps->which);
9470     if(currentlyInitializedVariant != gameInfo.variant) {
9471       char buf[MSG_SIZ];
9472         // [HGM] variantswitch: make engine aware of new variant
9473         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9474                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9475         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9476         SendToProgram(buf, cps);
9477         currentlyInitializedVariant = gameInfo.variant;
9478     }
9479     SendToProgram("force\n", cps);
9480     if (startedFromSetupPosition) {
9481         SendBoard(cps, backwardMostMove);
9482     if (appData.debugMode) {
9483         fprintf(debugFP, "feedMoves\n");
9484     }
9485     }
9486     for (i = backwardMostMove; i < upto; i++) {
9487         SendMoveToProgram(i, cps);
9488     }
9489 }
9490
9491
9492 void
9493 ResurrectChessProgram()
9494 {
9495      /* The chess program may have exited.
9496         If so, restart it and feed it all the moves made so far. */
9497
9498     if (appData.noChessProgram || first.pr != NoProc) return;
9499
9500     StartChessProgram(&first);
9501     InitChessProgram(&first, FALSE);
9502     FeedMovesToProgram(&first, currentMove);
9503
9504     if (!first.sendTime) {
9505         /* can't tell gnuchess what its clock should read,
9506            so we bow to its notion. */
9507         ResetClocks();
9508         timeRemaining[0][currentMove] = whiteTimeRemaining;
9509         timeRemaining[1][currentMove] = blackTimeRemaining;
9510     }
9511
9512     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9513                 appData.icsEngineAnalyze) && first.analysisSupport) {
9514       SendToProgram("analyze\n", &first);
9515       first.analyzing = TRUE;
9516     }
9517 }
9518
9519 /*
9520  * Button procedures
9521  */
9522 void
9523 Reset(redraw, init)
9524      int redraw, init;
9525 {
9526     int i;
9527
9528     if (appData.debugMode) {
9529         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9530                 redraw, init, gameMode);
9531     }
9532     CleanupTail(); // [HGM] vari: delete any stored variations
9533     pausing = pauseExamInvalid = FALSE;
9534     startedFromSetupPosition = blackPlaysFirst = FALSE;
9535     firstMove = TRUE;
9536     whiteFlag = blackFlag = FALSE;
9537     userOfferedDraw = FALSE;
9538     hintRequested = bookRequested = FALSE;
9539     first.maybeThinking = FALSE;
9540     second.maybeThinking = FALSE;
9541     first.bookSuspend = FALSE; // [HGM] book
9542     second.bookSuspend = FALSE;
9543     thinkOutput[0] = NULLCHAR;
9544     lastHint[0] = NULLCHAR;
9545     ClearGameInfo(&gameInfo);
9546     gameInfo.variant = StringToVariant(appData.variant);
9547     ics_user_moved = ics_clock_paused = FALSE;
9548     ics_getting_history = H_FALSE;
9549     ics_gamenum = -1;
9550     white_holding[0] = black_holding[0] = NULLCHAR;
9551     ClearProgramStats();
9552     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9553
9554     ResetFrontEnd();
9555     ClearHighlights();
9556     flipView = appData.flipView;
9557     ClearPremoveHighlights();
9558     gotPremove = FALSE;
9559     alarmSounded = FALSE;
9560
9561     GameEnds(EndOfFile, NULL, GE_PLAYER);
9562     if(appData.serverMovesName != NULL) {
9563         /* [HGM] prepare to make moves file for broadcasting */
9564         clock_t t = clock();
9565         if(serverMoves != NULL) fclose(serverMoves);
9566         serverMoves = fopen(appData.serverMovesName, "r");
9567         if(serverMoves != NULL) {
9568             fclose(serverMoves);
9569             /* delay 15 sec before overwriting, so all clients can see end */
9570             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9571         }
9572         serverMoves = fopen(appData.serverMovesName, "w");
9573     }
9574
9575     ExitAnalyzeMode();
9576     gameMode = BeginningOfGame;
9577     ModeHighlight();
9578     if(appData.icsActive) gameInfo.variant = VariantNormal;
9579     currentMove = forwardMostMove = backwardMostMove = 0;
9580     InitPosition(redraw);
9581     for (i = 0; i < MAX_MOVES; i++) {
9582         if (commentList[i] != NULL) {
9583             free(commentList[i]);
9584             commentList[i] = NULL;
9585         }
9586     }
9587     ResetClocks();
9588     timeRemaining[0][0] = whiteTimeRemaining;
9589     timeRemaining[1][0] = blackTimeRemaining;
9590     if (first.pr == NULL) {
9591         StartChessProgram(&first);
9592     }
9593     if (init) {
9594             InitChessProgram(&first, startedFromSetupPosition);
9595     }
9596     DisplayTitle("");
9597     DisplayMessage("", "");
9598     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9599     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9600 }
9601
9602 void
9603 AutoPlayGameLoop()
9604 {
9605     for (;;) {
9606         if (!AutoPlayOneMove())
9607           return;
9608         if (matchMode || appData.timeDelay == 0)
9609           continue;
9610         if (appData.timeDelay < 0)
9611           return;
9612         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9613         break;
9614     }
9615 }
9616
9617
9618 int
9619 AutoPlayOneMove()
9620 {
9621     int fromX, fromY, toX, toY;
9622
9623     if (appData.debugMode) {
9624       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9625     }
9626
9627     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9628       return FALSE;
9629
9630     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9631       pvInfoList[currentMove].depth = programStats.depth;
9632       pvInfoList[currentMove].score = programStats.score;
9633       pvInfoList[currentMove].time  = 0;
9634       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9635     }
9636
9637     if (currentMove >= forwardMostMove) {
9638       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9639       gameMode = EditGame;
9640       ModeHighlight();
9641
9642       /* [AS] Clear current move marker at the end of a game */
9643       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9644
9645       return FALSE;
9646     }
9647
9648     toX = moveList[currentMove][2] - AAA;
9649     toY = moveList[currentMove][3] - ONE;
9650
9651     if (moveList[currentMove][1] == '@') {
9652         if (appData.highlightLastMove) {
9653             SetHighlights(-1, -1, toX, toY);
9654         }
9655     } else {
9656         fromX = moveList[currentMove][0] - AAA;
9657         fromY = moveList[currentMove][1] - ONE;
9658
9659         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9660
9661         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9662
9663         if (appData.highlightLastMove) {
9664             SetHighlights(fromX, fromY, toX, toY);
9665         }
9666     }
9667     DisplayMove(currentMove);
9668     SendMoveToProgram(currentMove++, &first);
9669     DisplayBothClocks();
9670     DrawPosition(FALSE, boards[currentMove]);
9671     // [HGM] PV info: always display, routine tests if empty
9672     DisplayComment(currentMove - 1, commentList[currentMove]);
9673     return TRUE;
9674 }
9675
9676
9677 int
9678 LoadGameOneMove(readAhead)
9679      ChessMove readAhead;
9680 {
9681     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9682     char promoChar = NULLCHAR;
9683     ChessMove moveType;
9684     char move[MSG_SIZ];
9685     char *p, *q;
9686
9687     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9688         gameMode != AnalyzeMode && gameMode != Training) {
9689         gameFileFP = NULL;
9690         return FALSE;
9691     }
9692
9693     yyboardindex = forwardMostMove;
9694     if (readAhead != EndOfFile) {
9695       moveType = readAhead;
9696     } else {
9697       if (gameFileFP == NULL)
9698           return FALSE;
9699       moveType = (ChessMove) Myylex();
9700     }
9701
9702     done = FALSE;
9703     switch (moveType) {
9704       case Comment:
9705         if (appData.debugMode)
9706           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9707         p = yy_text;
9708
9709         /* append the comment but don't display it */
9710         AppendComment(currentMove, p, FALSE);
9711         return TRUE;
9712
9713       case WhiteCapturesEnPassant:
9714       case BlackCapturesEnPassant:
9715       case WhitePromotion:
9716       case BlackPromotion:
9717       case WhiteNonPromotion:
9718       case BlackNonPromotion:
9719       case NormalMove:
9720       case WhiteKingSideCastle:
9721       case WhiteQueenSideCastle:
9722       case BlackKingSideCastle:
9723       case BlackQueenSideCastle:
9724       case WhiteKingSideCastleWild:
9725       case WhiteQueenSideCastleWild:
9726       case BlackKingSideCastleWild:
9727       case BlackQueenSideCastleWild:
9728       /* PUSH Fabien */
9729       case WhiteHSideCastleFR:
9730       case WhiteASideCastleFR:
9731       case BlackHSideCastleFR:
9732       case BlackASideCastleFR:
9733       /* POP Fabien */
9734         if (appData.debugMode)
9735           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9736         fromX = currentMoveString[0] - AAA;
9737         fromY = currentMoveString[1] - ONE;
9738         toX = currentMoveString[2] - AAA;
9739         toY = currentMoveString[3] - ONE;
9740         promoChar = currentMoveString[4];
9741         break;
9742
9743       case WhiteDrop:
9744       case BlackDrop:
9745         if (appData.debugMode)
9746           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9747         fromX = moveType == WhiteDrop ?
9748           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9749         (int) CharToPiece(ToLower(currentMoveString[0]));
9750         fromY = DROP_RANK;
9751         toX = currentMoveString[2] - AAA;
9752         toY = currentMoveString[3] - ONE;
9753         break;
9754
9755       case WhiteWins:
9756       case BlackWins:
9757       case GameIsDrawn:
9758       case GameUnfinished:
9759         if (appData.debugMode)
9760           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9761         p = strchr(yy_text, '{');
9762         if (p == NULL) p = strchr(yy_text, '(');
9763         if (p == NULL) {
9764             p = yy_text;
9765             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9766         } else {
9767             q = strchr(p, *p == '{' ? '}' : ')');
9768             if (q != NULL) *q = NULLCHAR;
9769             p++;
9770         }
9771         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9772         GameEnds(moveType, p, GE_FILE);
9773         done = TRUE;
9774         if (cmailMsgLoaded) {
9775             ClearHighlights();
9776             flipView = WhiteOnMove(currentMove);
9777             if (moveType == GameUnfinished) flipView = !flipView;
9778             if (appData.debugMode)
9779               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9780         }
9781         break;
9782
9783       case EndOfFile:
9784         if (appData.debugMode)
9785           fprintf(debugFP, "Parser hit end of file\n");
9786         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9787           case MT_NONE:
9788           case MT_CHECK:
9789             break;
9790           case MT_CHECKMATE:
9791           case MT_STAINMATE:
9792             if (WhiteOnMove(currentMove)) {
9793                 GameEnds(BlackWins, "Black mates", GE_FILE);
9794             } else {
9795                 GameEnds(WhiteWins, "White mates", GE_FILE);
9796             }
9797             break;
9798           case MT_STALEMATE:
9799             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9800             break;
9801         }
9802         done = TRUE;
9803         break;
9804
9805       case MoveNumberOne:
9806         if (lastLoadGameStart == GNUChessGame) {
9807             /* GNUChessGames have numbers, but they aren't move numbers */
9808             if (appData.debugMode)
9809               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9810                       yy_text, (int) moveType);
9811             return LoadGameOneMove(EndOfFile); /* tail recursion */
9812         }
9813         /* else fall thru */
9814
9815       case XBoardGame:
9816       case GNUChessGame:
9817       case PGNTag:
9818         /* Reached start of next game in file */
9819         if (appData.debugMode)
9820           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9821         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9822           case MT_NONE:
9823           case MT_CHECK:
9824             break;
9825           case MT_CHECKMATE:
9826           case MT_STAINMATE:
9827             if (WhiteOnMove(currentMove)) {
9828                 GameEnds(BlackWins, "Black mates", GE_FILE);
9829             } else {
9830                 GameEnds(WhiteWins, "White mates", GE_FILE);
9831             }
9832             break;
9833           case MT_STALEMATE:
9834             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9835             break;
9836         }
9837         done = TRUE;
9838         break;
9839
9840       case PositionDiagram:     /* should not happen; ignore */
9841       case ElapsedTime:         /* ignore */
9842       case NAG:                 /* ignore */
9843         if (appData.debugMode)
9844           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9845                   yy_text, (int) moveType);
9846         return LoadGameOneMove(EndOfFile); /* tail recursion */
9847
9848       case IllegalMove:
9849         if (appData.testLegality) {
9850             if (appData.debugMode)
9851               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9852             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9853                     (forwardMostMove / 2) + 1,
9854                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9855             DisplayError(move, 0);
9856             done = TRUE;
9857         } else {
9858             if (appData.debugMode)
9859               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9860                       yy_text, currentMoveString);
9861             fromX = currentMoveString[0] - AAA;
9862             fromY = currentMoveString[1] - ONE;
9863             toX = currentMoveString[2] - AAA;
9864             toY = currentMoveString[3] - ONE;
9865             promoChar = currentMoveString[4];
9866         }
9867         break;
9868
9869       case AmbiguousMove:
9870         if (appData.debugMode)
9871           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9872         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9873                 (forwardMostMove / 2) + 1,
9874                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9875         DisplayError(move, 0);
9876         done = TRUE;
9877         break;
9878
9879       default:
9880       case ImpossibleMove:
9881         if (appData.debugMode)
9882           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9883         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9884                 (forwardMostMove / 2) + 1,
9885                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9886         DisplayError(move, 0);
9887         done = TRUE;
9888         break;
9889     }
9890
9891     if (done) {
9892         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9893             DrawPosition(FALSE, boards[currentMove]);
9894             DisplayBothClocks();
9895             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9896               DisplayComment(currentMove - 1, commentList[currentMove]);
9897         }
9898         (void) StopLoadGameTimer();
9899         gameFileFP = NULL;
9900         cmailOldMove = forwardMostMove;
9901         return FALSE;
9902     } else {
9903         /* currentMoveString is set as a side-effect of yylex */
9904         strcat(currentMoveString, "\n");
9905         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9906
9907         thinkOutput[0] = NULLCHAR;
9908         MakeMove(fromX, fromY, toX, toY, promoChar);
9909         currentMove = forwardMostMove;
9910         return TRUE;
9911     }
9912 }
9913
9914 /* Load the nth game from the given file */
9915 int
9916 LoadGameFromFile(filename, n, title, useList)
9917      char *filename;
9918      int n;
9919      char *title;
9920      /*Boolean*/ int useList;
9921 {
9922     FILE *f;
9923     char buf[MSG_SIZ];
9924
9925     if (strcmp(filename, "-") == 0) {
9926         f = stdin;
9927         title = "stdin";
9928     } else {
9929         f = fopen(filename, "rb");
9930         if (f == NULL) {
9931           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9932             DisplayError(buf, errno);
9933             return FALSE;
9934         }
9935     }
9936     if (fseek(f, 0, 0) == -1) {
9937         /* f is not seekable; probably a pipe */
9938         useList = FALSE;
9939     }
9940     if (useList && n == 0) {
9941         int error = GameListBuild(f);
9942         if (error) {
9943             DisplayError(_("Cannot build game list"), error);
9944         } else if (!ListEmpty(&gameList) &&
9945                    ((ListGame *) gameList.tailPred)->number > 1) {
9946             GameListPopUp(f, title);
9947             return TRUE;
9948         }
9949         GameListDestroy();
9950         n = 1;
9951     }
9952     if (n == 0) n = 1;
9953     return LoadGame(f, n, title, FALSE);
9954 }
9955
9956
9957 void
9958 MakeRegisteredMove()
9959 {
9960     int fromX, fromY, toX, toY;
9961     char promoChar;
9962     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9963         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9964           case CMAIL_MOVE:
9965           case CMAIL_DRAW:
9966             if (appData.debugMode)
9967               fprintf(debugFP, "Restoring %s for game %d\n",
9968                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9969
9970             thinkOutput[0] = NULLCHAR;
9971             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9972             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9973             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9974             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9975             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9976             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9977             MakeMove(fromX, fromY, toX, toY, promoChar);
9978             ShowMove(fromX, fromY, toX, toY);
9979
9980             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9981               case MT_NONE:
9982               case MT_CHECK:
9983                 break;
9984
9985               case MT_CHECKMATE:
9986               case MT_STAINMATE:
9987                 if (WhiteOnMove(currentMove)) {
9988                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9989                 } else {
9990                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9991                 }
9992                 break;
9993
9994               case MT_STALEMATE:
9995                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9996                 break;
9997             }
9998
9999             break;
10000
10001           case CMAIL_RESIGN:
10002             if (WhiteOnMove(currentMove)) {
10003                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10004             } else {
10005                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10006             }
10007             break;
10008
10009           case CMAIL_ACCEPT:
10010             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10011             break;
10012
10013           default:
10014             break;
10015         }
10016     }
10017
10018     return;
10019 }
10020
10021 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10022 int
10023 CmailLoadGame(f, gameNumber, title, useList)
10024      FILE *f;
10025      int gameNumber;
10026      char *title;
10027      int useList;
10028 {
10029     int retVal;
10030
10031     if (gameNumber > nCmailGames) {
10032         DisplayError(_("No more games in this message"), 0);
10033         return FALSE;
10034     }
10035     if (f == lastLoadGameFP) {
10036         int offset = gameNumber - lastLoadGameNumber;
10037         if (offset == 0) {
10038             cmailMsg[0] = NULLCHAR;
10039             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10040                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10041                 nCmailMovesRegistered--;
10042             }
10043             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10044             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10045                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10046             }
10047         } else {
10048             if (! RegisterMove()) return FALSE;
10049         }
10050     }
10051
10052     retVal = LoadGame(f, gameNumber, title, useList);
10053
10054     /* Make move registered during previous look at this game, if any */
10055     MakeRegisteredMove();
10056
10057     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10058         commentList[currentMove]
10059           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10060         DisplayComment(currentMove - 1, commentList[currentMove]);
10061     }
10062
10063     return retVal;
10064 }
10065
10066 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10067 int
10068 ReloadGame(offset)
10069      int offset;
10070 {
10071     int gameNumber = lastLoadGameNumber + offset;
10072     if (lastLoadGameFP == NULL) {
10073         DisplayError(_("No game has been loaded yet"), 0);
10074         return FALSE;
10075     }
10076     if (gameNumber <= 0) {
10077         DisplayError(_("Can't back up any further"), 0);
10078         return FALSE;
10079     }
10080     if (cmailMsgLoaded) {
10081         return CmailLoadGame(lastLoadGameFP, gameNumber,
10082                              lastLoadGameTitle, lastLoadGameUseList);
10083     } else {
10084         return LoadGame(lastLoadGameFP, gameNumber,
10085                         lastLoadGameTitle, lastLoadGameUseList);
10086     }
10087 }
10088
10089
10090
10091 /* Load the nth game from open file f */
10092 int
10093 LoadGame(f, gameNumber, title, useList)
10094      FILE *f;
10095      int gameNumber;
10096      char *title;
10097      int useList;
10098 {
10099     ChessMove cm;
10100     char buf[MSG_SIZ];
10101     int gn = gameNumber;
10102     ListGame *lg = NULL;
10103     int numPGNTags = 0;
10104     int err;
10105     GameMode oldGameMode;
10106     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10107
10108     if (appData.debugMode)
10109         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10110
10111     if (gameMode == Training )
10112         SetTrainingModeOff();
10113
10114     oldGameMode = gameMode;
10115     if (gameMode != BeginningOfGame) {
10116       Reset(FALSE, TRUE);
10117     }
10118
10119     gameFileFP = f;
10120     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10121         fclose(lastLoadGameFP);
10122     }
10123
10124     if (useList) {
10125         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10126
10127         if (lg) {
10128             fseek(f, lg->offset, 0);
10129             GameListHighlight(gameNumber);
10130             gn = 1;
10131         }
10132         else {
10133             DisplayError(_("Game number out of range"), 0);
10134             return FALSE;
10135         }
10136     } else {
10137         GameListDestroy();
10138         if (fseek(f, 0, 0) == -1) {
10139             if (f == lastLoadGameFP ?
10140                 gameNumber == lastLoadGameNumber + 1 :
10141                 gameNumber == 1) {
10142                 gn = 1;
10143             } else {
10144                 DisplayError(_("Can't seek on game file"), 0);
10145                 return FALSE;
10146             }
10147         }
10148     }
10149     lastLoadGameFP = f;
10150     lastLoadGameNumber = gameNumber;
10151     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10152     lastLoadGameUseList = useList;
10153
10154     yynewfile(f);
10155
10156     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10157       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10158                 lg->gameInfo.black);
10159             DisplayTitle(buf);
10160     } else if (*title != NULLCHAR) {
10161         if (gameNumber > 1) {
10162           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10163             DisplayTitle(buf);
10164         } else {
10165             DisplayTitle(title);
10166         }
10167     }
10168
10169     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10170         gameMode = PlayFromGameFile;
10171         ModeHighlight();
10172     }
10173
10174     currentMove = forwardMostMove = backwardMostMove = 0;
10175     CopyBoard(boards[0], initialPosition);
10176     StopClocks();
10177
10178     /*
10179      * Skip the first gn-1 games in the file.
10180      * Also skip over anything that precedes an identifiable
10181      * start of game marker, to avoid being confused by
10182      * garbage at the start of the file.  Currently
10183      * recognized start of game markers are the move number "1",
10184      * the pattern "gnuchess .* game", the pattern
10185      * "^[#;%] [^ ]* game file", and a PGN tag block.
10186      * A game that starts with one of the latter two patterns
10187      * will also have a move number 1, possibly
10188      * following a position diagram.
10189      * 5-4-02: Let's try being more lenient and allowing a game to
10190      * start with an unnumbered move.  Does that break anything?
10191      */
10192     cm = lastLoadGameStart = EndOfFile;
10193     while (gn > 0) {
10194         yyboardindex = forwardMostMove;
10195         cm = (ChessMove) Myylex();
10196         switch (cm) {
10197           case EndOfFile:
10198             if (cmailMsgLoaded) {
10199                 nCmailGames = CMAIL_MAX_GAMES - gn;
10200             } else {
10201                 Reset(TRUE, TRUE);
10202                 DisplayError(_("Game not found in file"), 0);
10203             }
10204             return FALSE;
10205
10206           case GNUChessGame:
10207           case XBoardGame:
10208             gn--;
10209             lastLoadGameStart = cm;
10210             break;
10211
10212           case MoveNumberOne:
10213             switch (lastLoadGameStart) {
10214               case GNUChessGame:
10215               case XBoardGame:
10216               case PGNTag:
10217                 break;
10218               case MoveNumberOne:
10219               case EndOfFile:
10220                 gn--;           /* count this game */
10221                 lastLoadGameStart = cm;
10222                 break;
10223               default:
10224                 /* impossible */
10225                 break;
10226             }
10227             break;
10228
10229           case PGNTag:
10230             switch (lastLoadGameStart) {
10231               case GNUChessGame:
10232               case PGNTag:
10233               case MoveNumberOne:
10234               case EndOfFile:
10235                 gn--;           /* count this game */
10236                 lastLoadGameStart = cm;
10237                 break;
10238               case XBoardGame:
10239                 lastLoadGameStart = cm; /* game counted already */
10240                 break;
10241               default:
10242                 /* impossible */
10243                 break;
10244             }
10245             if (gn > 0) {
10246                 do {
10247                     yyboardindex = forwardMostMove;
10248                     cm = (ChessMove) Myylex();
10249                 } while (cm == PGNTag || cm == Comment);
10250             }
10251             break;
10252
10253           case WhiteWins:
10254           case BlackWins:
10255           case GameIsDrawn:
10256             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10257                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10258                     != CMAIL_OLD_RESULT) {
10259                     nCmailResults ++ ;
10260                     cmailResult[  CMAIL_MAX_GAMES
10261                                 - gn - 1] = CMAIL_OLD_RESULT;
10262                 }
10263             }
10264             break;
10265
10266           case NormalMove:
10267             /* Only a NormalMove can be at the start of a game
10268              * without a position diagram. */
10269             if (lastLoadGameStart == EndOfFile ) {
10270               gn--;
10271               lastLoadGameStart = MoveNumberOne;
10272             }
10273             break;
10274
10275           default:
10276             break;
10277         }
10278     }
10279
10280     if (appData.debugMode)
10281       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10282
10283     if (cm == XBoardGame) {
10284         /* Skip any header junk before position diagram and/or move 1 */
10285         for (;;) {
10286             yyboardindex = forwardMostMove;
10287             cm = (ChessMove) Myylex();
10288
10289             if (cm == EndOfFile ||
10290                 cm == GNUChessGame || cm == XBoardGame) {
10291                 /* Empty game; pretend end-of-file and handle later */
10292                 cm = EndOfFile;
10293                 break;
10294             }
10295
10296             if (cm == MoveNumberOne || cm == PositionDiagram ||
10297                 cm == PGNTag || cm == Comment)
10298               break;
10299         }
10300     } else if (cm == GNUChessGame) {
10301         if (gameInfo.event != NULL) {
10302             free(gameInfo.event);
10303         }
10304         gameInfo.event = StrSave(yy_text);
10305     }
10306
10307     startedFromSetupPosition = FALSE;
10308     while (cm == PGNTag) {
10309         if (appData.debugMode)
10310           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10311         err = ParsePGNTag(yy_text, &gameInfo);
10312         if (!err) numPGNTags++;
10313
10314         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10315         if(gameInfo.variant != oldVariant) {
10316             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10317             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10318             InitPosition(TRUE);
10319             oldVariant = gameInfo.variant;
10320             if (appData.debugMode)
10321               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10322         }
10323
10324
10325         if (gameInfo.fen != NULL) {
10326           Board initial_position;
10327           startedFromSetupPosition = TRUE;
10328           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10329             Reset(TRUE, TRUE);
10330             DisplayError(_("Bad FEN position in file"), 0);
10331             return FALSE;
10332           }
10333           CopyBoard(boards[0], initial_position);
10334           if (blackPlaysFirst) {
10335             currentMove = forwardMostMove = backwardMostMove = 1;
10336             CopyBoard(boards[1], initial_position);
10337             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10338             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10339             timeRemaining[0][1] = whiteTimeRemaining;
10340             timeRemaining[1][1] = blackTimeRemaining;
10341             if (commentList[0] != NULL) {
10342               commentList[1] = commentList[0];
10343               commentList[0] = NULL;
10344             }
10345           } else {
10346             currentMove = forwardMostMove = backwardMostMove = 0;
10347           }
10348           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10349           {   int i;
10350               initialRulePlies = FENrulePlies;
10351               for( i=0; i< nrCastlingRights; i++ )
10352                   initialRights[i] = initial_position[CASTLING][i];
10353           }
10354           yyboardindex = forwardMostMove;
10355           free(gameInfo.fen);
10356           gameInfo.fen = NULL;
10357         }
10358
10359         yyboardindex = forwardMostMove;
10360         cm = (ChessMove) Myylex();
10361
10362         /* Handle comments interspersed among the tags */
10363         while (cm == Comment) {
10364             char *p;
10365             if (appData.debugMode)
10366               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10367             p = yy_text;
10368             AppendComment(currentMove, p, FALSE);
10369             yyboardindex = forwardMostMove;
10370             cm = (ChessMove) Myylex();
10371         }
10372     }
10373
10374     /* don't rely on existence of Event tag since if game was
10375      * pasted from clipboard the Event tag may not exist
10376      */
10377     if (numPGNTags > 0){
10378         char *tags;
10379         if (gameInfo.variant == VariantNormal) {
10380           VariantClass v = StringToVariant(gameInfo.event);
10381           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10382           if(v < VariantShogi) gameInfo.variant = v;
10383         }
10384         if (!matchMode) {
10385           if( appData.autoDisplayTags ) {
10386             tags = PGNTags(&gameInfo);
10387             TagsPopUp(tags, CmailMsg());
10388             free(tags);
10389           }
10390         }
10391     } else {
10392         /* Make something up, but don't display it now */
10393         SetGameInfo();
10394         TagsPopDown();
10395     }
10396
10397     if (cm == PositionDiagram) {
10398         int i, j;
10399         char *p;
10400         Board initial_position;
10401
10402         if (appData.debugMode)
10403           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10404
10405         if (!startedFromSetupPosition) {
10406             p = yy_text;
10407             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10408               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10409                 switch (*p) {
10410                   case '[':
10411                   case '-':
10412                   case ' ':
10413                   case '\t':
10414                   case '\n':
10415                   case '\r':
10416                     break;
10417                   default:
10418                     initial_position[i][j++] = CharToPiece(*p);
10419                     break;
10420                 }
10421             while (*p == ' ' || *p == '\t' ||
10422                    *p == '\n' || *p == '\r') p++;
10423
10424             if (strncmp(p, "black", strlen("black"))==0)
10425               blackPlaysFirst = TRUE;
10426             else
10427               blackPlaysFirst = FALSE;
10428             startedFromSetupPosition = TRUE;
10429
10430             CopyBoard(boards[0], initial_position);
10431             if (blackPlaysFirst) {
10432                 currentMove = forwardMostMove = backwardMostMove = 1;
10433                 CopyBoard(boards[1], initial_position);
10434                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10435                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10436                 timeRemaining[0][1] = whiteTimeRemaining;
10437                 timeRemaining[1][1] = blackTimeRemaining;
10438                 if (commentList[0] != NULL) {
10439                     commentList[1] = commentList[0];
10440                     commentList[0] = NULL;
10441                 }
10442             } else {
10443                 currentMove = forwardMostMove = backwardMostMove = 0;
10444             }
10445         }
10446         yyboardindex = forwardMostMove;
10447         cm = (ChessMove) Myylex();
10448     }
10449
10450     if (first.pr == NoProc) {
10451         StartChessProgram(&first);
10452     }
10453     InitChessProgram(&first, FALSE);
10454     SendToProgram("force\n", &first);
10455     if (startedFromSetupPosition) {
10456         SendBoard(&first, forwardMostMove);
10457     if (appData.debugMode) {
10458         fprintf(debugFP, "Load Game\n");
10459     }
10460         DisplayBothClocks();
10461     }
10462
10463     /* [HGM] server: flag to write setup moves in broadcast file as one */
10464     loadFlag = appData.suppressLoadMoves;
10465
10466     while (cm == Comment) {
10467         char *p;
10468         if (appData.debugMode)
10469           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10470         p = yy_text;
10471         AppendComment(currentMove, p, FALSE);
10472         yyboardindex = forwardMostMove;
10473         cm = (ChessMove) Myylex();
10474     }
10475
10476     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10477         cm == WhiteWins || cm == BlackWins ||
10478         cm == GameIsDrawn || cm == GameUnfinished) {
10479         DisplayMessage("", _("No moves in game"));
10480         if (cmailMsgLoaded) {
10481             if (appData.debugMode)
10482               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10483             ClearHighlights();
10484             flipView = FALSE;
10485         }
10486         DrawPosition(FALSE, boards[currentMove]);
10487         DisplayBothClocks();
10488         gameMode = EditGame;
10489         ModeHighlight();
10490         gameFileFP = NULL;
10491         cmailOldMove = 0;
10492         return TRUE;
10493     }
10494
10495     // [HGM] PV info: routine tests if comment empty
10496     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10497         DisplayComment(currentMove - 1, commentList[currentMove]);
10498     }
10499     if (!matchMode && appData.timeDelay != 0)
10500       DrawPosition(FALSE, boards[currentMove]);
10501
10502     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10503       programStats.ok_to_send = 1;
10504     }
10505
10506     /* if the first token after the PGN tags is a move
10507      * and not move number 1, retrieve it from the parser
10508      */
10509     if (cm != MoveNumberOne)
10510         LoadGameOneMove(cm);
10511
10512     /* load the remaining moves from the file */
10513     while (LoadGameOneMove(EndOfFile)) {
10514       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10515       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10516     }
10517
10518     /* rewind to the start of the game */
10519     currentMove = backwardMostMove;
10520
10521     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10522
10523     if (oldGameMode == AnalyzeFile ||
10524         oldGameMode == AnalyzeMode) {
10525       AnalyzeFileEvent();
10526     }
10527
10528     if (matchMode || appData.timeDelay == 0) {
10529       ToEndEvent();
10530       gameMode = EditGame;
10531       ModeHighlight();
10532     } else if (appData.timeDelay > 0) {
10533       AutoPlayGameLoop();
10534     }
10535
10536     if (appData.debugMode)
10537         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10538
10539     loadFlag = 0; /* [HGM] true game starts */
10540     return TRUE;
10541 }
10542
10543 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10544 int
10545 ReloadPosition(offset)
10546      int offset;
10547 {
10548     int positionNumber = lastLoadPositionNumber + offset;
10549     if (lastLoadPositionFP == NULL) {
10550         DisplayError(_("No position has been loaded yet"), 0);
10551         return FALSE;
10552     }
10553     if (positionNumber <= 0) {
10554         DisplayError(_("Can't back up any further"), 0);
10555         return FALSE;
10556     }
10557     return LoadPosition(lastLoadPositionFP, positionNumber,
10558                         lastLoadPositionTitle);
10559 }
10560
10561 /* Load the nth position from the given file */
10562 int
10563 LoadPositionFromFile(filename, n, title)
10564      char *filename;
10565      int n;
10566      char *title;
10567 {
10568     FILE *f;
10569     char buf[MSG_SIZ];
10570
10571     if (strcmp(filename, "-") == 0) {
10572         return LoadPosition(stdin, n, "stdin");
10573     } else {
10574         f = fopen(filename, "rb");
10575         if (f == NULL) {
10576             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10577             DisplayError(buf, errno);
10578             return FALSE;
10579         } else {
10580             return LoadPosition(f, n, title);
10581         }
10582     }
10583 }
10584
10585 /* Load the nth position from the given open file, and close it */
10586 int
10587 LoadPosition(f, positionNumber, title)
10588      FILE *f;
10589      int positionNumber;
10590      char *title;
10591 {
10592     char *p, line[MSG_SIZ];
10593     Board initial_position;
10594     int i, j, fenMode, pn;
10595
10596     if (gameMode == Training )
10597         SetTrainingModeOff();
10598
10599     if (gameMode != BeginningOfGame) {
10600         Reset(FALSE, TRUE);
10601     }
10602     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10603         fclose(lastLoadPositionFP);
10604     }
10605     if (positionNumber == 0) positionNumber = 1;
10606     lastLoadPositionFP = f;
10607     lastLoadPositionNumber = positionNumber;
10608     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10609     if (first.pr == NoProc) {
10610       StartChessProgram(&first);
10611       InitChessProgram(&first, FALSE);
10612     }
10613     pn = positionNumber;
10614     if (positionNumber < 0) {
10615         /* Negative position number means to seek to that byte offset */
10616         if (fseek(f, -positionNumber, 0) == -1) {
10617             DisplayError(_("Can't seek on position file"), 0);
10618             return FALSE;
10619         };
10620         pn = 1;
10621     } else {
10622         if (fseek(f, 0, 0) == -1) {
10623             if (f == lastLoadPositionFP ?
10624                 positionNumber == lastLoadPositionNumber + 1 :
10625                 positionNumber == 1) {
10626                 pn = 1;
10627             } else {
10628                 DisplayError(_("Can't seek on position file"), 0);
10629                 return FALSE;
10630             }
10631         }
10632     }
10633     /* See if this file is FEN or old-style xboard */
10634     if (fgets(line, MSG_SIZ, f) == NULL) {
10635         DisplayError(_("Position not found in file"), 0);
10636         return FALSE;
10637     }
10638     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10639     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10640
10641     if (pn >= 2) {
10642         if (fenMode || line[0] == '#') pn--;
10643         while (pn > 0) {
10644             /* skip positions before number pn */
10645             if (fgets(line, MSG_SIZ, f) == NULL) {
10646                 Reset(TRUE, TRUE);
10647                 DisplayError(_("Position not found in file"), 0);
10648                 return FALSE;
10649             }
10650             if (fenMode || line[0] == '#') pn--;
10651         }
10652     }
10653
10654     if (fenMode) {
10655         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10656             DisplayError(_("Bad FEN position in file"), 0);
10657             return FALSE;
10658         }
10659     } else {
10660         (void) fgets(line, MSG_SIZ, f);
10661         (void) fgets(line, MSG_SIZ, f);
10662
10663         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10664             (void) fgets(line, MSG_SIZ, f);
10665             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10666                 if (*p == ' ')
10667                   continue;
10668                 initial_position[i][j++] = CharToPiece(*p);
10669             }
10670         }
10671
10672         blackPlaysFirst = FALSE;
10673         if (!feof(f)) {
10674             (void) fgets(line, MSG_SIZ, f);
10675             if (strncmp(line, "black", strlen("black"))==0)
10676               blackPlaysFirst = TRUE;
10677         }
10678     }
10679     startedFromSetupPosition = TRUE;
10680
10681     SendToProgram("force\n", &first);
10682     CopyBoard(boards[0], initial_position);
10683     if (blackPlaysFirst) {
10684         currentMove = forwardMostMove = backwardMostMove = 1;
10685         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10686         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10687         CopyBoard(boards[1], initial_position);
10688         DisplayMessage("", _("Black to play"));
10689     } else {
10690         currentMove = forwardMostMove = backwardMostMove = 0;
10691         DisplayMessage("", _("White to play"));
10692     }
10693     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10694     SendBoard(&first, forwardMostMove);
10695     if (appData.debugMode) {
10696 int i, j;
10697   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10698   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10699         fprintf(debugFP, "Load Position\n");
10700     }
10701
10702     if (positionNumber > 1) {
10703       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10704         DisplayTitle(line);
10705     } else {
10706         DisplayTitle(title);
10707     }
10708     gameMode = EditGame;
10709     ModeHighlight();
10710     ResetClocks();
10711     timeRemaining[0][1] = whiteTimeRemaining;
10712     timeRemaining[1][1] = blackTimeRemaining;
10713     DrawPosition(FALSE, boards[currentMove]);
10714
10715     return TRUE;
10716 }
10717
10718
10719 void
10720 CopyPlayerNameIntoFileName(dest, src)
10721      char **dest, *src;
10722 {
10723     while (*src != NULLCHAR && *src != ',') {
10724         if (*src == ' ') {
10725             *(*dest)++ = '_';
10726             src++;
10727         } else {
10728             *(*dest)++ = *src++;
10729         }
10730     }
10731 }
10732
10733 char *DefaultFileName(ext)
10734      char *ext;
10735 {
10736     static char def[MSG_SIZ];
10737     char *p;
10738
10739     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10740         p = def;
10741         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10742         *p++ = '-';
10743         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10744         *p++ = '.';
10745         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10746     } else {
10747         def[0] = NULLCHAR;
10748     }
10749     return def;
10750 }
10751
10752 /* Save the current game to the given file */
10753 int
10754 SaveGameToFile(filename, append)
10755      char *filename;
10756      int append;
10757 {
10758     FILE *f;
10759     char buf[MSG_SIZ];
10760
10761     if (strcmp(filename, "-") == 0) {
10762         return SaveGame(stdout, 0, NULL);
10763     } else {
10764         f = fopen(filename, append ? "a" : "w");
10765         if (f == NULL) {
10766             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10767             DisplayError(buf, errno);
10768             return FALSE;
10769         } else {
10770             return SaveGame(f, 0, NULL);
10771         }
10772     }
10773 }
10774
10775 char *
10776 SavePart(str)
10777      char *str;
10778 {
10779     static char buf[MSG_SIZ];
10780     char *p;
10781
10782     p = strchr(str, ' ');
10783     if (p == NULL) return str;
10784     strncpy(buf, str, p - str);
10785     buf[p - str] = NULLCHAR;
10786     return buf;
10787 }
10788
10789 #define PGN_MAX_LINE 75
10790
10791 #define PGN_SIDE_WHITE  0
10792 #define PGN_SIDE_BLACK  1
10793
10794 /* [AS] */
10795 static int FindFirstMoveOutOfBook( int side )
10796 {
10797     int result = -1;
10798
10799     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10800         int index = backwardMostMove;
10801         int has_book_hit = 0;
10802
10803         if( (index % 2) != side ) {
10804             index++;
10805         }
10806
10807         while( index < forwardMostMove ) {
10808             /* Check to see if engine is in book */
10809             int depth = pvInfoList[index].depth;
10810             int score = pvInfoList[index].score;
10811             int in_book = 0;
10812
10813             if( depth <= 2 ) {
10814                 in_book = 1;
10815             }
10816             else if( score == 0 && depth == 63 ) {
10817                 in_book = 1; /* Zappa */
10818             }
10819             else if( score == 2 && depth == 99 ) {
10820                 in_book = 1; /* Abrok */
10821             }
10822
10823             has_book_hit += in_book;
10824
10825             if( ! in_book ) {
10826                 result = index;
10827
10828                 break;
10829             }
10830
10831             index += 2;
10832         }
10833     }
10834
10835     return result;
10836 }
10837
10838 /* [AS] */
10839 void GetOutOfBookInfo( char * buf )
10840 {
10841     int oob[2];
10842     int i;
10843     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10844
10845     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10846     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10847
10848     *buf = '\0';
10849
10850     if( oob[0] >= 0 || oob[1] >= 0 ) {
10851         for( i=0; i<2; i++ ) {
10852             int idx = oob[i];
10853
10854             if( idx >= 0 ) {
10855                 if( i > 0 && oob[0] >= 0 ) {
10856                     strcat( buf, "   " );
10857                 }
10858
10859                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10860                 sprintf( buf+strlen(buf), "%s%.2f",
10861                     pvInfoList[idx].score >= 0 ? "+" : "",
10862                     pvInfoList[idx].score / 100.0 );
10863             }
10864         }
10865     }
10866 }
10867
10868 /* Save game in PGN style and close the file */
10869 int
10870 SaveGamePGN(f)
10871      FILE *f;
10872 {
10873     int i, offset, linelen, newblock;
10874     time_t tm;
10875 //    char *movetext;
10876     char numtext[32];
10877     int movelen, numlen, blank;
10878     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10879
10880     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10881
10882     tm = time((time_t *) NULL);
10883
10884     PrintPGNTags(f, &gameInfo);
10885
10886     if (backwardMostMove > 0 || startedFromSetupPosition) {
10887         char *fen = PositionToFEN(backwardMostMove, NULL);
10888         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10889         fprintf(f, "\n{--------------\n");
10890         PrintPosition(f, backwardMostMove);
10891         fprintf(f, "--------------}\n");
10892         free(fen);
10893     }
10894     else {
10895         /* [AS] Out of book annotation */
10896         if( appData.saveOutOfBookInfo ) {
10897             char buf[64];
10898
10899             GetOutOfBookInfo( buf );
10900
10901             if( buf[0] != '\0' ) {
10902                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10903             }
10904         }
10905
10906         fprintf(f, "\n");
10907     }
10908
10909     i = backwardMostMove;
10910     linelen = 0;
10911     newblock = TRUE;
10912
10913     while (i < forwardMostMove) {
10914         /* Print comments preceding this move */
10915         if (commentList[i] != NULL) {
10916             if (linelen > 0) fprintf(f, "\n");
10917             fprintf(f, "%s", commentList[i]);
10918             linelen = 0;
10919             newblock = TRUE;
10920         }
10921
10922         /* Format move number */
10923         if ((i % 2) == 0)
10924           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10925         else
10926           if (newblock)
10927             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10928           else
10929             numtext[0] = NULLCHAR;
10930
10931         numlen = strlen(numtext);
10932         newblock = FALSE;
10933
10934         /* Print move number */
10935         blank = linelen > 0 && numlen > 0;
10936         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10937             fprintf(f, "\n");
10938             linelen = 0;
10939             blank = 0;
10940         }
10941         if (blank) {
10942             fprintf(f, " ");
10943             linelen++;
10944         }
10945         fprintf(f, "%s", numtext);
10946         linelen += numlen;
10947
10948         /* Get move */
10949         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10950         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10951
10952         /* Print move */
10953         blank = linelen > 0 && movelen > 0;
10954         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10955             fprintf(f, "\n");
10956             linelen = 0;
10957             blank = 0;
10958         }
10959         if (blank) {
10960             fprintf(f, " ");
10961             linelen++;
10962         }
10963         fprintf(f, "%s", move_buffer);
10964         linelen += movelen;
10965
10966         /* [AS] Add PV info if present */
10967         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10968             /* [HGM] add time */
10969             char buf[MSG_SIZ]; int seconds;
10970
10971             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10972
10973             if( seconds <= 0)
10974               buf[0] = 0;
10975             else
10976               if( seconds < 30 )
10977                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10978               else
10979                 {
10980                   seconds = (seconds + 4)/10; // round to full seconds
10981                   if( seconds < 60 )
10982                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10983                   else
10984                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10985                 }
10986
10987             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10988                       pvInfoList[i].score >= 0 ? "+" : "",
10989                       pvInfoList[i].score / 100.0,
10990                       pvInfoList[i].depth,
10991                       buf );
10992
10993             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10994
10995             /* Print score/depth */
10996             blank = linelen > 0 && movelen > 0;
10997             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10998                 fprintf(f, "\n");
10999                 linelen = 0;
11000                 blank = 0;
11001             }
11002             if (blank) {
11003                 fprintf(f, " ");
11004                 linelen++;
11005             }
11006             fprintf(f, "%s", move_buffer);
11007             linelen += movelen;
11008         }
11009
11010         i++;
11011     }
11012
11013     /* Start a new line */
11014     if (linelen > 0) fprintf(f, "\n");
11015
11016     /* Print comments after last move */
11017     if (commentList[i] != NULL) {
11018         fprintf(f, "%s\n", commentList[i]);
11019     }
11020
11021     /* Print result */
11022     if (gameInfo.resultDetails != NULL &&
11023         gameInfo.resultDetails[0] != NULLCHAR) {
11024         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11025                 PGNResult(gameInfo.result));
11026     } else {
11027         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11028     }
11029
11030     fclose(f);
11031     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11032     return TRUE;
11033 }
11034
11035 /* Save game in old style and close the file */
11036 int
11037 SaveGameOldStyle(f)
11038      FILE *f;
11039 {
11040     int i, offset;
11041     time_t tm;
11042
11043     tm = time((time_t *) NULL);
11044
11045     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11046     PrintOpponents(f);
11047
11048     if (backwardMostMove > 0 || startedFromSetupPosition) {
11049         fprintf(f, "\n[--------------\n");
11050         PrintPosition(f, backwardMostMove);
11051         fprintf(f, "--------------]\n");
11052     } else {
11053         fprintf(f, "\n");
11054     }
11055
11056     i = backwardMostMove;
11057     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11058
11059     while (i < forwardMostMove) {
11060         if (commentList[i] != NULL) {
11061             fprintf(f, "[%s]\n", commentList[i]);
11062         }
11063
11064         if ((i % 2) == 1) {
11065             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11066             i++;
11067         } else {
11068             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11069             i++;
11070             if (commentList[i] != NULL) {
11071                 fprintf(f, "\n");
11072                 continue;
11073             }
11074             if (i >= forwardMostMove) {
11075                 fprintf(f, "\n");
11076                 break;
11077             }
11078             fprintf(f, "%s\n", parseList[i]);
11079             i++;
11080         }
11081     }
11082
11083     if (commentList[i] != NULL) {
11084         fprintf(f, "[%s]\n", commentList[i]);
11085     }
11086
11087     /* This isn't really the old style, but it's close enough */
11088     if (gameInfo.resultDetails != NULL &&
11089         gameInfo.resultDetails[0] != NULLCHAR) {
11090         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11091                 gameInfo.resultDetails);
11092     } else {
11093         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11094     }
11095
11096     fclose(f);
11097     return TRUE;
11098 }
11099
11100 /* Save the current game to open file f and close the file */
11101 int
11102 SaveGame(f, dummy, dummy2)
11103      FILE *f;
11104      int dummy;
11105      char *dummy2;
11106 {
11107     if (gameMode == EditPosition) EditPositionDone(TRUE);
11108     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11109     if (appData.oldSaveStyle)
11110       return SaveGameOldStyle(f);
11111     else
11112       return SaveGamePGN(f);
11113 }
11114
11115 /* Save the current position to the given file */
11116 int
11117 SavePositionToFile(filename)
11118      char *filename;
11119 {
11120     FILE *f;
11121     char buf[MSG_SIZ];
11122
11123     if (strcmp(filename, "-") == 0) {
11124         return SavePosition(stdout, 0, NULL);
11125     } else {
11126         f = fopen(filename, "a");
11127         if (f == NULL) {
11128             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11129             DisplayError(buf, errno);
11130             return FALSE;
11131         } else {
11132             SavePosition(f, 0, NULL);
11133             return TRUE;
11134         }
11135     }
11136 }
11137
11138 /* Save the current position to the given open file and close the file */
11139 int
11140 SavePosition(f, dummy, dummy2)
11141      FILE *f;
11142      int dummy;
11143      char *dummy2;
11144 {
11145     time_t tm;
11146     char *fen;
11147
11148     if (gameMode == EditPosition) EditPositionDone(TRUE);
11149     if (appData.oldSaveStyle) {
11150         tm = time((time_t *) NULL);
11151
11152         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11153         PrintOpponents(f);
11154         fprintf(f, "[--------------\n");
11155         PrintPosition(f, currentMove);
11156         fprintf(f, "--------------]\n");
11157     } else {
11158         fen = PositionToFEN(currentMove, NULL);
11159         fprintf(f, "%s\n", fen);
11160         free(fen);
11161     }
11162     fclose(f);
11163     return TRUE;
11164 }
11165
11166 void
11167 ReloadCmailMsgEvent(unregister)
11168      int unregister;
11169 {
11170 #if !WIN32
11171     static char *inFilename = NULL;
11172     static char *outFilename;
11173     int i;
11174     struct stat inbuf, outbuf;
11175     int status;
11176
11177     /* Any registered moves are unregistered if unregister is set, */
11178     /* i.e. invoked by the signal handler */
11179     if (unregister) {
11180         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11181             cmailMoveRegistered[i] = FALSE;
11182             if (cmailCommentList[i] != NULL) {
11183                 free(cmailCommentList[i]);
11184                 cmailCommentList[i] = NULL;
11185             }
11186         }
11187         nCmailMovesRegistered = 0;
11188     }
11189
11190     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11191         cmailResult[i] = CMAIL_NOT_RESULT;
11192     }
11193     nCmailResults = 0;
11194
11195     if (inFilename == NULL) {
11196         /* Because the filenames are static they only get malloced once  */
11197         /* and they never get freed                                      */
11198         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11199         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11200
11201         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11202         sprintf(outFilename, "%s.out", appData.cmailGameName);
11203     }
11204
11205     status = stat(outFilename, &outbuf);
11206     if (status < 0) {
11207         cmailMailedMove = FALSE;
11208     } else {
11209         status = stat(inFilename, &inbuf);
11210         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11211     }
11212
11213     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11214        counts the games, notes how each one terminated, etc.
11215
11216        It would be nice to remove this kludge and instead gather all
11217        the information while building the game list.  (And to keep it
11218        in the game list nodes instead of having a bunch of fixed-size
11219        parallel arrays.)  Note this will require getting each game's
11220        termination from the PGN tags, as the game list builder does
11221        not process the game moves.  --mann
11222        */
11223     cmailMsgLoaded = TRUE;
11224     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11225
11226     /* Load first game in the file or popup game menu */
11227     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11228
11229 #endif /* !WIN32 */
11230     return;
11231 }
11232
11233 int
11234 RegisterMove()
11235 {
11236     FILE *f;
11237     char string[MSG_SIZ];
11238
11239     if (   cmailMailedMove
11240         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11241         return TRUE;            /* Allow free viewing  */
11242     }
11243
11244     /* Unregister move to ensure that we don't leave RegisterMove        */
11245     /* with the move registered when the conditions for registering no   */
11246     /* longer hold                                                       */
11247     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11248         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11249         nCmailMovesRegistered --;
11250
11251         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11252           {
11253               free(cmailCommentList[lastLoadGameNumber - 1]);
11254               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11255           }
11256     }
11257
11258     if (cmailOldMove == -1) {
11259         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11260         return FALSE;
11261     }
11262
11263     if (currentMove > cmailOldMove + 1) {
11264         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11265         return FALSE;
11266     }
11267
11268     if (currentMove < cmailOldMove) {
11269         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11270         return FALSE;
11271     }
11272
11273     if (forwardMostMove > currentMove) {
11274         /* Silently truncate extra moves */
11275         TruncateGame();
11276     }
11277
11278     if (   (currentMove == cmailOldMove + 1)
11279         || (   (currentMove == cmailOldMove)
11280             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11281                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11282         if (gameInfo.result != GameUnfinished) {
11283             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11284         }
11285
11286         if (commentList[currentMove] != NULL) {
11287             cmailCommentList[lastLoadGameNumber - 1]
11288               = StrSave(commentList[currentMove]);
11289         }
11290         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11291
11292         if (appData.debugMode)
11293           fprintf(debugFP, "Saving %s for game %d\n",
11294                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11295
11296         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11297
11298         f = fopen(string, "w");
11299         if (appData.oldSaveStyle) {
11300             SaveGameOldStyle(f); /* also closes the file */
11301
11302             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11303             f = fopen(string, "w");
11304             SavePosition(f, 0, NULL); /* also closes the file */
11305         } else {
11306             fprintf(f, "{--------------\n");
11307             PrintPosition(f, currentMove);
11308             fprintf(f, "--------------}\n\n");
11309
11310             SaveGame(f, 0, NULL); /* also closes the file*/
11311         }
11312
11313         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11314         nCmailMovesRegistered ++;
11315     } else if (nCmailGames == 1) {
11316         DisplayError(_("You have not made a move yet"), 0);
11317         return FALSE;
11318     }
11319
11320     return TRUE;
11321 }
11322
11323 void
11324 MailMoveEvent()
11325 {
11326 #if !WIN32
11327     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11328     FILE *commandOutput;
11329     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11330     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11331     int nBuffers;
11332     int i;
11333     int archived;
11334     char *arcDir;
11335
11336     if (! cmailMsgLoaded) {
11337         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11338         return;
11339     }
11340
11341     if (nCmailGames == nCmailResults) {
11342         DisplayError(_("No unfinished games"), 0);
11343         return;
11344     }
11345
11346 #if CMAIL_PROHIBIT_REMAIL
11347     if (cmailMailedMove) {
11348       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);
11349         DisplayError(msg, 0);
11350         return;
11351     }
11352 #endif
11353
11354     if (! (cmailMailedMove || RegisterMove())) return;
11355
11356     if (   cmailMailedMove
11357         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11358       snprintf(string, MSG_SIZ, partCommandString,
11359                appData.debugMode ? " -v" : "", appData.cmailGameName);
11360         commandOutput = popen(string, "r");
11361
11362         if (commandOutput == NULL) {
11363             DisplayError(_("Failed to invoke cmail"), 0);
11364         } else {
11365             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11366                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11367             }
11368             if (nBuffers > 1) {
11369                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11370                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11371                 nBytes = MSG_SIZ - 1;
11372             } else {
11373                 (void) memcpy(msg, buffer, nBytes);
11374             }
11375             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11376
11377             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11378                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11379
11380                 archived = TRUE;
11381                 for (i = 0; i < nCmailGames; i ++) {
11382                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11383                         archived = FALSE;
11384                     }
11385                 }
11386                 if (   archived
11387                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11388                         != NULL)) {
11389                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11390                            arcDir,
11391                            appData.cmailGameName,
11392                            gameInfo.date);
11393                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11394                     cmailMsgLoaded = FALSE;
11395                 }
11396             }
11397
11398             DisplayInformation(msg);
11399             pclose(commandOutput);
11400         }
11401     } else {
11402         if ((*cmailMsg) != '\0') {
11403             DisplayInformation(cmailMsg);
11404         }
11405     }
11406
11407     return;
11408 #endif /* !WIN32 */
11409 }
11410
11411 char *
11412 CmailMsg()
11413 {
11414 #if WIN32
11415     return NULL;
11416 #else
11417     int  prependComma = 0;
11418     char number[5];
11419     char string[MSG_SIZ];       /* Space for game-list */
11420     int  i;
11421
11422     if (!cmailMsgLoaded) return "";
11423
11424     if (cmailMailedMove) {
11425       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11426     } else {
11427         /* Create a list of games left */
11428       snprintf(string, MSG_SIZ, "[");
11429         for (i = 0; i < nCmailGames; i ++) {
11430             if (! (   cmailMoveRegistered[i]
11431                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11432                 if (prependComma) {
11433                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11434                 } else {
11435                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11436                     prependComma = 1;
11437                 }
11438
11439                 strcat(string, number);
11440             }
11441         }
11442         strcat(string, "]");
11443
11444         if (nCmailMovesRegistered + nCmailResults == 0) {
11445             switch (nCmailGames) {
11446               case 1:
11447                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11448                 break;
11449
11450               case 2:
11451                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11452                 break;
11453
11454               default:
11455                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11456                          nCmailGames);
11457                 break;
11458             }
11459         } else {
11460             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11461               case 1:
11462                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11463                          string);
11464                 break;
11465
11466               case 0:
11467                 if (nCmailResults == nCmailGames) {
11468                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11469                 } else {
11470                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11471                 }
11472                 break;
11473
11474               default:
11475                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11476                          string);
11477             }
11478         }
11479     }
11480     return cmailMsg;
11481 #endif /* WIN32 */
11482 }
11483
11484 void
11485 ResetGameEvent()
11486 {
11487     if (gameMode == Training)
11488       SetTrainingModeOff();
11489
11490     Reset(TRUE, TRUE);
11491     cmailMsgLoaded = FALSE;
11492     if (appData.icsActive) {
11493       SendToICS(ics_prefix);
11494       SendToICS("refresh\n");
11495     }
11496 }
11497
11498 void
11499 ExitEvent(status)
11500      int status;
11501 {
11502     exiting++;
11503     if (exiting > 2) {
11504       /* Give up on clean exit */
11505       exit(status);
11506     }
11507     if (exiting > 1) {
11508       /* Keep trying for clean exit */
11509       return;
11510     }
11511
11512     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11513
11514     if (telnetISR != NULL) {
11515       RemoveInputSource(telnetISR);
11516     }
11517     if (icsPR != NoProc) {
11518       DestroyChildProcess(icsPR, TRUE);
11519     }
11520
11521     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11522     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11523
11524     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11525     /* make sure this other one finishes before killing it!                  */
11526     if(endingGame) { int count = 0;
11527         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11528         while(endingGame && count++ < 10) DoSleep(1);
11529         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11530     }
11531
11532     /* Kill off chess programs */
11533     if (first.pr != NoProc) {
11534         ExitAnalyzeMode();
11535
11536         DoSleep( appData.delayBeforeQuit );
11537         SendToProgram("quit\n", &first);
11538         DoSleep( appData.delayAfterQuit );
11539         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11540     }
11541     if (second.pr != NoProc) {
11542         DoSleep( appData.delayBeforeQuit );
11543         SendToProgram("quit\n", &second);
11544         DoSleep( appData.delayAfterQuit );
11545         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11546     }
11547     if (first.isr != NULL) {
11548         RemoveInputSource(first.isr);
11549     }
11550     if (second.isr != NULL) {
11551         RemoveInputSource(second.isr);
11552     }
11553
11554     ShutDownFrontEnd();
11555     exit(status);
11556 }
11557
11558 void
11559 PauseEvent()
11560 {
11561     if (appData.debugMode)
11562         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11563     if (pausing) {
11564         pausing = FALSE;
11565         ModeHighlight();
11566         if (gameMode == MachinePlaysWhite ||
11567             gameMode == MachinePlaysBlack) {
11568             StartClocks();
11569         } else {
11570             DisplayBothClocks();
11571         }
11572         if (gameMode == PlayFromGameFile) {
11573             if (appData.timeDelay >= 0)
11574                 AutoPlayGameLoop();
11575         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11576             Reset(FALSE, TRUE);
11577             SendToICS(ics_prefix);
11578             SendToICS("refresh\n");
11579         } else if (currentMove < forwardMostMove) {
11580             ForwardInner(forwardMostMove);
11581         }
11582         pauseExamInvalid = FALSE;
11583     } else {
11584         switch (gameMode) {
11585           default:
11586             return;
11587           case IcsExamining:
11588             pauseExamForwardMostMove = forwardMostMove;
11589             pauseExamInvalid = FALSE;
11590             /* fall through */
11591           case IcsObserving:
11592           case IcsPlayingWhite:
11593           case IcsPlayingBlack:
11594             pausing = TRUE;
11595             ModeHighlight();
11596             return;
11597           case PlayFromGameFile:
11598             (void) StopLoadGameTimer();
11599             pausing = TRUE;
11600             ModeHighlight();
11601             break;
11602           case BeginningOfGame:
11603             if (appData.icsActive) return;
11604             /* else fall through */
11605           case MachinePlaysWhite:
11606           case MachinePlaysBlack:
11607           case TwoMachinesPlay:
11608             if (forwardMostMove == 0)
11609               return;           /* don't pause if no one has moved */
11610             if ((gameMode == MachinePlaysWhite &&
11611                  !WhiteOnMove(forwardMostMove)) ||
11612                 (gameMode == MachinePlaysBlack &&
11613                  WhiteOnMove(forwardMostMove))) {
11614                 StopClocks();
11615             }
11616             pausing = TRUE;
11617             ModeHighlight();
11618             break;
11619         }
11620     }
11621 }
11622
11623 void
11624 EditCommentEvent()
11625 {
11626     char title[MSG_SIZ];
11627
11628     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11629       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11630     } else {
11631       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11632                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11633                parseList[currentMove - 1]);
11634     }
11635
11636     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11637 }
11638
11639
11640 void
11641 EditTagsEvent()
11642 {
11643     char *tags = PGNTags(&gameInfo);
11644     EditTagsPopUp(tags);
11645     free(tags);
11646 }
11647
11648 void
11649 AnalyzeModeEvent()
11650 {
11651     if (appData.noChessProgram || gameMode == AnalyzeMode)
11652       return;
11653
11654     if (gameMode != AnalyzeFile) {
11655         if (!appData.icsEngineAnalyze) {
11656                EditGameEvent();
11657                if (gameMode != EditGame) return;
11658         }
11659         ResurrectChessProgram();
11660         SendToProgram("analyze\n", &first);
11661         first.analyzing = TRUE;
11662         /*first.maybeThinking = TRUE;*/
11663         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11664         EngineOutputPopUp();
11665     }
11666     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11667     pausing = FALSE;
11668     ModeHighlight();
11669     SetGameInfo();
11670
11671     StartAnalysisClock();
11672     GetTimeMark(&lastNodeCountTime);
11673     lastNodeCount = 0;
11674 }
11675
11676 void
11677 AnalyzeFileEvent()
11678 {
11679     if (appData.noChessProgram || gameMode == AnalyzeFile)
11680       return;
11681
11682     if (gameMode != AnalyzeMode) {
11683         EditGameEvent();
11684         if (gameMode != EditGame) return;
11685         ResurrectChessProgram();
11686         SendToProgram("analyze\n", &first);
11687         first.analyzing = TRUE;
11688         /*first.maybeThinking = TRUE;*/
11689         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11690         EngineOutputPopUp();
11691     }
11692     gameMode = AnalyzeFile;
11693     pausing = FALSE;
11694     ModeHighlight();
11695     SetGameInfo();
11696
11697     StartAnalysisClock();
11698     GetTimeMark(&lastNodeCountTime);
11699     lastNodeCount = 0;
11700 }
11701
11702 void
11703 MachineWhiteEvent()
11704 {
11705     char buf[MSG_SIZ];
11706     char *bookHit = NULL;
11707
11708     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11709       return;
11710
11711
11712     if (gameMode == PlayFromGameFile ||
11713         gameMode == TwoMachinesPlay  ||
11714         gameMode == Training         ||
11715         gameMode == AnalyzeMode      ||
11716         gameMode == EndOfGame)
11717         EditGameEvent();
11718
11719     if (gameMode == EditPosition)
11720         EditPositionDone(TRUE);
11721
11722     if (!WhiteOnMove(currentMove)) {
11723         DisplayError(_("It is not White's turn"), 0);
11724         return;
11725     }
11726
11727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11728       ExitAnalyzeMode();
11729
11730     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11731         gameMode == AnalyzeFile)
11732         TruncateGame();
11733
11734     ResurrectChessProgram();    /* in case it isn't running */
11735     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11736         gameMode = MachinePlaysWhite;
11737         ResetClocks();
11738     } else
11739     gameMode = MachinePlaysWhite;
11740     pausing = FALSE;
11741     ModeHighlight();
11742     SetGameInfo();
11743     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11744     DisplayTitle(buf);
11745     if (first.sendName) {
11746       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11747       SendToProgram(buf, &first);
11748     }
11749     if (first.sendTime) {
11750       if (first.useColors) {
11751         SendToProgram("black\n", &first); /*gnu kludge*/
11752       }
11753       SendTimeRemaining(&first, TRUE);
11754     }
11755     if (first.useColors) {
11756       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11757     }
11758     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11759     SetMachineThinkingEnables();
11760     first.maybeThinking = TRUE;
11761     StartClocks();
11762     firstMove = FALSE;
11763
11764     if (appData.autoFlipView && !flipView) {
11765       flipView = !flipView;
11766       DrawPosition(FALSE, NULL);
11767       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11768     }
11769
11770     if(bookHit) { // [HGM] book: simulate book reply
11771         static char bookMove[MSG_SIZ]; // a bit generous?
11772
11773         programStats.nodes = programStats.depth = programStats.time =
11774         programStats.score = programStats.got_only_move = 0;
11775         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11776
11777         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11778         strcat(bookMove, bookHit);
11779         HandleMachineMove(bookMove, &first);
11780     }
11781 }
11782
11783 void
11784 MachineBlackEvent()
11785 {
11786   char buf[MSG_SIZ];
11787   char *bookHit = NULL;
11788
11789     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11790         return;
11791
11792
11793     if (gameMode == PlayFromGameFile ||
11794         gameMode == TwoMachinesPlay  ||
11795         gameMode == Training         ||
11796         gameMode == AnalyzeMode      ||
11797         gameMode == EndOfGame)
11798         EditGameEvent();
11799
11800     if (gameMode == EditPosition)
11801         EditPositionDone(TRUE);
11802
11803     if (WhiteOnMove(currentMove)) {
11804         DisplayError(_("It is not Black's turn"), 0);
11805         return;
11806     }
11807
11808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11809       ExitAnalyzeMode();
11810
11811     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11812         gameMode == AnalyzeFile)
11813         TruncateGame();
11814
11815     ResurrectChessProgram();    /* in case it isn't running */
11816     gameMode = MachinePlaysBlack;
11817     pausing = FALSE;
11818     ModeHighlight();
11819     SetGameInfo();
11820     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11821     DisplayTitle(buf);
11822     if (first.sendName) {
11823       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11824       SendToProgram(buf, &first);
11825     }
11826     if (first.sendTime) {
11827       if (first.useColors) {
11828         SendToProgram("white\n", &first); /*gnu kludge*/
11829       }
11830       SendTimeRemaining(&first, FALSE);
11831     }
11832     if (first.useColors) {
11833       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11834     }
11835     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11836     SetMachineThinkingEnables();
11837     first.maybeThinking = TRUE;
11838     StartClocks();
11839
11840     if (appData.autoFlipView && flipView) {
11841       flipView = !flipView;
11842       DrawPosition(FALSE, NULL);
11843       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11844     }
11845     if(bookHit) { // [HGM] book: simulate book reply
11846         static char bookMove[MSG_SIZ]; // a bit generous?
11847
11848         programStats.nodes = programStats.depth = programStats.time =
11849         programStats.score = programStats.got_only_move = 0;
11850         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11851
11852         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11853         strcat(bookMove, bookHit);
11854         HandleMachineMove(bookMove, &first);
11855     }
11856 }
11857
11858
11859 void
11860 DisplayTwoMachinesTitle()
11861 {
11862     char buf[MSG_SIZ];
11863     if (appData.matchGames > 0) {
11864         if (first.twoMachinesColor[0] == 'w') {
11865           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11866                    gameInfo.white, gameInfo.black,
11867                    first.matchWins, second.matchWins,
11868                    matchGame - 1 - (first.matchWins + second.matchWins));
11869         } else {
11870           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11871                    gameInfo.white, gameInfo.black,
11872                    second.matchWins, first.matchWins,
11873                    matchGame - 1 - (first.matchWins + second.matchWins));
11874         }
11875     } else {
11876       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11877     }
11878     DisplayTitle(buf);
11879 }
11880
11881 void
11882 SettingsMenuIfReady()
11883 {
11884   if (second.lastPing != second.lastPong) {
11885     DisplayMessage("", _("Waiting for second chess program"));
11886     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11887     return;
11888   }
11889   ThawUI();
11890   DisplayMessage("", "");
11891   SettingsPopUp(&second);
11892 }
11893
11894 int
11895 WaitForSecond(DelayedEventCallback retry)
11896 {
11897     if (second.pr == NULL) {
11898         StartChessProgram(&second);
11899         if (second.protocolVersion == 1) {
11900           retry();
11901         } else {
11902           /* kludge: allow timeout for initial "feature" command */
11903           FreezeUI();
11904           DisplayMessage("", _("Starting second chess program"));
11905           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11906         }
11907         return 1;
11908     }
11909     return 0;
11910 }
11911
11912 void
11913 TwoMachinesEvent P((void))
11914 {
11915     int i;
11916     char buf[MSG_SIZ];
11917     ChessProgramState *onmove;
11918     char *bookHit = NULL;
11919
11920     if (appData.noChessProgram) return;
11921
11922     switch (gameMode) {
11923       case TwoMachinesPlay:
11924         return;
11925       case MachinePlaysWhite:
11926       case MachinePlaysBlack:
11927         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11928             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11929             return;
11930         }
11931         /* fall through */
11932       case BeginningOfGame:
11933       case PlayFromGameFile:
11934       case EndOfGame:
11935         EditGameEvent();
11936         if (gameMode != EditGame) return;
11937         break;
11938       case EditPosition:
11939         EditPositionDone(TRUE);
11940         break;
11941       case AnalyzeMode:
11942       case AnalyzeFile:
11943         ExitAnalyzeMode();
11944         break;
11945       case EditGame:
11946       default:
11947         break;
11948     }
11949
11950 //    forwardMostMove = currentMove;
11951     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11952     ResurrectChessProgram();    /* in case first program isn't running */
11953
11954     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11955     DisplayMessage("", "");
11956     InitChessProgram(&second, FALSE);
11957     SendToProgram("force\n", &second);
11958     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11959       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11960       return;
11961     }
11962     if (startedFromSetupPosition) {
11963         SendBoard(&second, backwardMostMove);
11964     if (appData.debugMode) {
11965         fprintf(debugFP, "Two Machines\n");
11966     }
11967     }
11968     for (i = backwardMostMove; i < forwardMostMove; i++) {
11969         SendMoveToProgram(i, &second);
11970     }
11971
11972     gameMode = TwoMachinesPlay;
11973     pausing = FALSE;
11974     ModeHighlight();
11975     SetGameInfo();
11976     DisplayTwoMachinesTitle();
11977     firstMove = TRUE;
11978     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11979         onmove = &first;
11980     } else {
11981         onmove = &second;
11982     }
11983
11984     SendToProgram(first.computerString, &first);
11985     if (first.sendName) {
11986       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11987       SendToProgram(buf, &first);
11988     }
11989     SendToProgram(second.computerString, &second);
11990     if (second.sendName) {
11991       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11992       SendToProgram(buf, &second);
11993     }
11994
11995     ResetClocks();
11996     if (!first.sendTime || !second.sendTime) {
11997         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11998         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11999     }
12000     if (onmove->sendTime) {
12001       if (onmove->useColors) {
12002         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12003       }
12004       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12005     }
12006     if (onmove->useColors) {
12007       SendToProgram(onmove->twoMachinesColor, onmove);
12008     }
12009     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12010 //    SendToProgram("go\n", onmove);
12011     onmove->maybeThinking = TRUE;
12012     SetMachineThinkingEnables();
12013
12014     StartClocks();
12015
12016     if(bookHit) { // [HGM] book: simulate book reply
12017         static char bookMove[MSG_SIZ]; // a bit generous?
12018
12019         programStats.nodes = programStats.depth = programStats.time =
12020         programStats.score = programStats.got_only_move = 0;
12021         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12022
12023         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12024         strcat(bookMove, bookHit);
12025         savedMessage = bookMove; // args for deferred call
12026         savedState = onmove;
12027         ScheduleDelayedEvent(DeferredBookMove, 1);
12028     }
12029 }
12030
12031 void
12032 TrainingEvent()
12033 {
12034     if (gameMode == Training) {
12035       SetTrainingModeOff();
12036       gameMode = PlayFromGameFile;
12037       DisplayMessage("", _("Training mode off"));
12038     } else {
12039       gameMode = Training;
12040       animateTraining = appData.animate;
12041
12042       /* make sure we are not already at the end of the game */
12043       if (currentMove < forwardMostMove) {
12044         SetTrainingModeOn();
12045         DisplayMessage("", _("Training mode on"));
12046       } else {
12047         gameMode = PlayFromGameFile;
12048         DisplayError(_("Already at end of game"), 0);
12049       }
12050     }
12051     ModeHighlight();
12052 }
12053
12054 void
12055 IcsClientEvent()
12056 {
12057     if (!appData.icsActive) return;
12058     switch (gameMode) {
12059       case IcsPlayingWhite:
12060       case IcsPlayingBlack:
12061       case IcsObserving:
12062       case IcsIdle:
12063       case BeginningOfGame:
12064       case IcsExamining:
12065         return;
12066
12067       case EditGame:
12068         break;
12069
12070       case EditPosition:
12071         EditPositionDone(TRUE);
12072         break;
12073
12074       case AnalyzeMode:
12075       case AnalyzeFile:
12076         ExitAnalyzeMode();
12077         break;
12078
12079       default:
12080         EditGameEvent();
12081         break;
12082     }
12083
12084     gameMode = IcsIdle;
12085     ModeHighlight();
12086     return;
12087 }
12088
12089
12090 void
12091 EditGameEvent()
12092 {
12093     int i;
12094
12095     switch (gameMode) {
12096       case Training:
12097         SetTrainingModeOff();
12098         break;
12099       case MachinePlaysWhite:
12100       case MachinePlaysBlack:
12101       case BeginningOfGame:
12102         SendToProgram("force\n", &first);
12103         SetUserThinkingEnables();
12104         break;
12105       case PlayFromGameFile:
12106         (void) StopLoadGameTimer();
12107         if (gameFileFP != NULL) {
12108             gameFileFP = NULL;
12109         }
12110         break;
12111       case EditPosition:
12112         EditPositionDone(TRUE);
12113         break;
12114       case AnalyzeMode:
12115       case AnalyzeFile:
12116         ExitAnalyzeMode();
12117         SendToProgram("force\n", &first);
12118         break;
12119       case TwoMachinesPlay:
12120         GameEnds(EndOfFile, NULL, GE_PLAYER);
12121         ResurrectChessProgram();
12122         SetUserThinkingEnables();
12123         break;
12124       case EndOfGame:
12125         ResurrectChessProgram();
12126         break;
12127       case IcsPlayingBlack:
12128       case IcsPlayingWhite:
12129         DisplayError(_("Warning: You are still playing a game"), 0);
12130         break;
12131       case IcsObserving:
12132         DisplayError(_("Warning: You are still observing a game"), 0);
12133         break;
12134       case IcsExamining:
12135         DisplayError(_("Warning: You are still examining a game"), 0);
12136         break;
12137       case IcsIdle:
12138         break;
12139       case EditGame:
12140       default:
12141         return;
12142     }
12143
12144     pausing = FALSE;
12145     StopClocks();
12146     first.offeredDraw = second.offeredDraw = 0;
12147
12148     if (gameMode == PlayFromGameFile) {
12149         whiteTimeRemaining = timeRemaining[0][currentMove];
12150         blackTimeRemaining = timeRemaining[1][currentMove];
12151         DisplayTitle("");
12152     }
12153
12154     if (gameMode == MachinePlaysWhite ||
12155         gameMode == MachinePlaysBlack ||
12156         gameMode == TwoMachinesPlay ||
12157         gameMode == EndOfGame) {
12158         i = forwardMostMove;
12159         while (i > currentMove) {
12160             SendToProgram("undo\n", &first);
12161             i--;
12162         }
12163         whiteTimeRemaining = timeRemaining[0][currentMove];
12164         blackTimeRemaining = timeRemaining[1][currentMove];
12165         DisplayBothClocks();
12166         if (whiteFlag || blackFlag) {
12167             whiteFlag = blackFlag = 0;
12168         }
12169         DisplayTitle("");
12170     }
12171
12172     gameMode = EditGame;
12173     ModeHighlight();
12174     SetGameInfo();
12175 }
12176
12177
12178 void
12179 EditPositionEvent()
12180 {
12181     if (gameMode == EditPosition) {
12182         EditGameEvent();
12183         return;
12184     }
12185
12186     EditGameEvent();
12187     if (gameMode != EditGame) return;
12188
12189     gameMode = EditPosition;
12190     ModeHighlight();
12191     SetGameInfo();
12192     if (currentMove > 0)
12193       CopyBoard(boards[0], boards[currentMove]);
12194
12195     blackPlaysFirst = !WhiteOnMove(currentMove);
12196     ResetClocks();
12197     currentMove = forwardMostMove = backwardMostMove = 0;
12198     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12199     DisplayMove(-1);
12200 }
12201
12202 void
12203 ExitAnalyzeMode()
12204 {
12205     /* [DM] icsEngineAnalyze - possible call from other functions */
12206     if (appData.icsEngineAnalyze) {
12207         appData.icsEngineAnalyze = FALSE;
12208
12209         DisplayMessage("",_("Close ICS engine analyze..."));
12210     }
12211     if (first.analysisSupport && first.analyzing) {
12212       SendToProgram("exit\n", &first);
12213       first.analyzing = FALSE;
12214     }
12215     thinkOutput[0] = NULLCHAR;
12216 }
12217
12218 void
12219 EditPositionDone(Boolean fakeRights)
12220 {
12221     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12222
12223     startedFromSetupPosition = TRUE;
12224     InitChessProgram(&first, FALSE);
12225     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12226       boards[0][EP_STATUS] = EP_NONE;
12227       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12228     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12229         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12230         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12231       } else boards[0][CASTLING][2] = NoRights;
12232     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12233         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12234         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12235       } else boards[0][CASTLING][5] = NoRights;
12236     }
12237     SendToProgram("force\n", &first);
12238     if (blackPlaysFirst) {
12239         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12240         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12241         currentMove = forwardMostMove = backwardMostMove = 1;
12242         CopyBoard(boards[1], boards[0]);
12243     } else {
12244         currentMove = forwardMostMove = backwardMostMove = 0;
12245     }
12246     SendBoard(&first, forwardMostMove);
12247     if (appData.debugMode) {
12248         fprintf(debugFP, "EditPosDone\n");
12249     }
12250     DisplayTitle("");
12251     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12252     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12253     gameMode = EditGame;
12254     ModeHighlight();
12255     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12256     ClearHighlights(); /* [AS] */
12257 }
12258
12259 /* Pause for `ms' milliseconds */
12260 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12261 void
12262 TimeDelay(ms)
12263      long ms;
12264 {
12265     TimeMark m1, m2;
12266
12267     GetTimeMark(&m1);
12268     do {
12269         GetTimeMark(&m2);
12270     } while (SubtractTimeMarks(&m2, &m1) < ms);
12271 }
12272
12273 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12274 void
12275 SendMultiLineToICS(buf)
12276      char *buf;
12277 {
12278     char temp[MSG_SIZ+1], *p;
12279     int len;
12280
12281     len = strlen(buf);
12282     if (len > MSG_SIZ)
12283       len = MSG_SIZ;
12284
12285     strncpy(temp, buf, len);
12286     temp[len] = 0;
12287
12288     p = temp;
12289     while (*p) {
12290         if (*p == '\n' || *p == '\r')
12291           *p = ' ';
12292         ++p;
12293     }
12294
12295     strcat(temp, "\n");
12296     SendToICS(temp);
12297     SendToPlayer(temp, strlen(temp));
12298 }
12299
12300 void
12301 SetWhiteToPlayEvent()
12302 {
12303     if (gameMode == EditPosition) {
12304         blackPlaysFirst = FALSE;
12305         DisplayBothClocks();    /* works because currentMove is 0 */
12306     } else if (gameMode == IcsExamining) {
12307         SendToICS(ics_prefix);
12308         SendToICS("tomove white\n");
12309     }
12310 }
12311
12312 void
12313 SetBlackToPlayEvent()
12314 {
12315     if (gameMode == EditPosition) {
12316         blackPlaysFirst = TRUE;
12317         currentMove = 1;        /* kludge */
12318         DisplayBothClocks();
12319         currentMove = 0;
12320     } else if (gameMode == IcsExamining) {
12321         SendToICS(ics_prefix);
12322         SendToICS("tomove black\n");
12323     }
12324 }
12325
12326 void
12327 EditPositionMenuEvent(selection, x, y)
12328      ChessSquare selection;
12329      int x, y;
12330 {
12331     char buf[MSG_SIZ];
12332     ChessSquare piece = boards[0][y][x];
12333
12334     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12335
12336     switch (selection) {
12337       case ClearBoard:
12338         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12339             SendToICS(ics_prefix);
12340             SendToICS("bsetup clear\n");
12341         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12342             SendToICS(ics_prefix);
12343             SendToICS("clearboard\n");
12344         } else {
12345             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12346                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12347                 for (y = 0; y < BOARD_HEIGHT; y++) {
12348                     if (gameMode == IcsExamining) {
12349                         if (boards[currentMove][y][x] != EmptySquare) {
12350                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12351                                     AAA + x, ONE + y);
12352                             SendToICS(buf);
12353                         }
12354                     } else {
12355                         boards[0][y][x] = p;
12356                     }
12357                 }
12358             }
12359         }
12360         if (gameMode == EditPosition) {
12361             DrawPosition(FALSE, boards[0]);
12362         }
12363         break;
12364
12365       case WhitePlay:
12366         SetWhiteToPlayEvent();
12367         break;
12368
12369       case BlackPlay:
12370         SetBlackToPlayEvent();
12371         break;
12372
12373       case EmptySquare:
12374         if (gameMode == IcsExamining) {
12375             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12376             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12377             SendToICS(buf);
12378         } else {
12379             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12380                 if(x == BOARD_LEFT-2) {
12381                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12382                     boards[0][y][1] = 0;
12383                 } else
12384                 if(x == BOARD_RGHT+1) {
12385                     if(y >= gameInfo.holdingsSize) break;
12386                     boards[0][y][BOARD_WIDTH-2] = 0;
12387                 } else break;
12388             }
12389             boards[0][y][x] = EmptySquare;
12390             DrawPosition(FALSE, boards[0]);
12391         }
12392         break;
12393
12394       case PromotePiece:
12395         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12396            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12397             selection = (ChessSquare) (PROMOTED piece);
12398         } else if(piece == EmptySquare) selection = WhiteSilver;
12399         else selection = (ChessSquare)((int)piece - 1);
12400         goto defaultlabel;
12401
12402       case DemotePiece:
12403         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12404            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12405             selection = (ChessSquare) (DEMOTED piece);
12406         } else if(piece == EmptySquare) selection = BlackSilver;
12407         else selection = (ChessSquare)((int)piece + 1);
12408         goto defaultlabel;
12409
12410       case WhiteQueen:
12411       case BlackQueen:
12412         if(gameInfo.variant == VariantShatranj ||
12413            gameInfo.variant == VariantXiangqi  ||
12414            gameInfo.variant == VariantCourier  ||
12415            gameInfo.variant == VariantMakruk     )
12416             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12417         goto defaultlabel;
12418
12419       case WhiteKing:
12420       case BlackKing:
12421         if(gameInfo.variant == VariantXiangqi)
12422             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12423         if(gameInfo.variant == VariantKnightmate)
12424             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12425       default:
12426         defaultlabel:
12427         if (gameMode == IcsExamining) {
12428             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12429             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12430                      PieceToChar(selection), AAA + x, ONE + y);
12431             SendToICS(buf);
12432         } else {
12433             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12434                 int n;
12435                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12436                     n = PieceToNumber(selection - BlackPawn);
12437                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12438                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12439                     boards[0][BOARD_HEIGHT-1-n][1]++;
12440                 } else
12441                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12442                     n = PieceToNumber(selection);
12443                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12444                     boards[0][n][BOARD_WIDTH-1] = selection;
12445                     boards[0][n][BOARD_WIDTH-2]++;
12446                 }
12447             } else
12448             boards[0][y][x] = selection;
12449             DrawPosition(TRUE, boards[0]);
12450         }
12451         break;
12452     }
12453 }
12454
12455
12456 void
12457 DropMenuEvent(selection, x, y)
12458      ChessSquare selection;
12459      int x, y;
12460 {
12461     ChessMove moveType;
12462
12463     switch (gameMode) {
12464       case IcsPlayingWhite:
12465       case MachinePlaysBlack:
12466         if (!WhiteOnMove(currentMove)) {
12467             DisplayMoveError(_("It is Black's turn"));
12468             return;
12469         }
12470         moveType = WhiteDrop;
12471         break;
12472       case IcsPlayingBlack:
12473       case MachinePlaysWhite:
12474         if (WhiteOnMove(currentMove)) {
12475             DisplayMoveError(_("It is White's turn"));
12476             return;
12477         }
12478         moveType = BlackDrop;
12479         break;
12480       case EditGame:
12481         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12482         break;
12483       default:
12484         return;
12485     }
12486
12487     if (moveType == BlackDrop && selection < BlackPawn) {
12488       selection = (ChessSquare) ((int) selection
12489                                  + (int) BlackPawn - (int) WhitePawn);
12490     }
12491     if (boards[currentMove][y][x] != EmptySquare) {
12492         DisplayMoveError(_("That square is occupied"));
12493         return;
12494     }
12495
12496     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12497 }
12498
12499 void
12500 AcceptEvent()
12501 {
12502     /* Accept a pending offer of any kind from opponent */
12503
12504     if (appData.icsActive) {
12505         SendToICS(ics_prefix);
12506         SendToICS("accept\n");
12507     } else if (cmailMsgLoaded) {
12508         if (currentMove == cmailOldMove &&
12509             commentList[cmailOldMove] != NULL &&
12510             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12511                    "Black offers a draw" : "White offers a draw")) {
12512             TruncateGame();
12513             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12514             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12515         } else {
12516             DisplayError(_("There is no pending offer on this move"), 0);
12517             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12518         }
12519     } else {
12520         /* Not used for offers from chess program */
12521     }
12522 }
12523
12524 void
12525 DeclineEvent()
12526 {
12527     /* Decline a pending offer of any kind from opponent */
12528
12529     if (appData.icsActive) {
12530         SendToICS(ics_prefix);
12531         SendToICS("decline\n");
12532     } else if (cmailMsgLoaded) {
12533         if (currentMove == cmailOldMove &&
12534             commentList[cmailOldMove] != NULL &&
12535             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12536                    "Black offers a draw" : "White offers a draw")) {
12537 #ifdef NOTDEF
12538             AppendComment(cmailOldMove, "Draw declined", TRUE);
12539             DisplayComment(cmailOldMove - 1, "Draw declined");
12540 #endif /*NOTDEF*/
12541         } else {
12542             DisplayError(_("There is no pending offer on this move"), 0);
12543         }
12544     } else {
12545         /* Not used for offers from chess program */
12546     }
12547 }
12548
12549 void
12550 RematchEvent()
12551 {
12552     /* Issue ICS rematch command */
12553     if (appData.icsActive) {
12554         SendToICS(ics_prefix);
12555         SendToICS("rematch\n");
12556     }
12557 }
12558
12559 void
12560 CallFlagEvent()
12561 {
12562     /* Call your opponent's flag (claim a win on time) */
12563     if (appData.icsActive) {
12564         SendToICS(ics_prefix);
12565         SendToICS("flag\n");
12566     } else {
12567         switch (gameMode) {
12568           default:
12569             return;
12570           case MachinePlaysWhite:
12571             if (whiteFlag) {
12572                 if (blackFlag)
12573                   GameEnds(GameIsDrawn, "Both players ran out of time",
12574                            GE_PLAYER);
12575                 else
12576                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12577             } else {
12578                 DisplayError(_("Your opponent is not out of time"), 0);
12579             }
12580             break;
12581           case MachinePlaysBlack:
12582             if (blackFlag) {
12583                 if (whiteFlag)
12584                   GameEnds(GameIsDrawn, "Both players ran out of time",
12585                            GE_PLAYER);
12586                 else
12587                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12588             } else {
12589                 DisplayError(_("Your opponent is not out of time"), 0);
12590             }
12591             break;
12592         }
12593     }
12594 }
12595
12596 void
12597 DrawEvent()
12598 {
12599     /* Offer draw or accept pending draw offer from opponent */
12600
12601     if (appData.icsActive) {
12602         /* Note: tournament rules require draw offers to be
12603            made after you make your move but before you punch
12604            your clock.  Currently ICS doesn't let you do that;
12605            instead, you immediately punch your clock after making
12606            a move, but you can offer a draw at any time. */
12607
12608         SendToICS(ics_prefix);
12609         SendToICS("draw\n");
12610         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12611     } else if (cmailMsgLoaded) {
12612         if (currentMove == cmailOldMove &&
12613             commentList[cmailOldMove] != NULL &&
12614             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12615                    "Black offers a draw" : "White offers a draw")) {
12616             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12617             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12618         } else if (currentMove == cmailOldMove + 1) {
12619             char *offer = WhiteOnMove(cmailOldMove) ?
12620               "White offers a draw" : "Black offers a draw";
12621             AppendComment(currentMove, offer, TRUE);
12622             DisplayComment(currentMove - 1, offer);
12623             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12624         } else {
12625             DisplayError(_("You must make your move before offering a draw"), 0);
12626             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12627         }
12628     } else if (first.offeredDraw) {
12629         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12630     } else {
12631         if (first.sendDrawOffers) {
12632             SendToProgram("draw\n", &first);
12633             userOfferedDraw = TRUE;
12634         }
12635     }
12636 }
12637
12638 void
12639 AdjournEvent()
12640 {
12641     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12642
12643     if (appData.icsActive) {
12644         SendToICS(ics_prefix);
12645         SendToICS("adjourn\n");
12646     } else {
12647         /* Currently GNU Chess doesn't offer or accept Adjourns */
12648     }
12649 }
12650
12651
12652 void
12653 AbortEvent()
12654 {
12655     /* Offer Abort or accept pending Abort offer from opponent */
12656
12657     if (appData.icsActive) {
12658         SendToICS(ics_prefix);
12659         SendToICS("abort\n");
12660     } else {
12661         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12662     }
12663 }
12664
12665 void
12666 ResignEvent()
12667 {
12668     /* Resign.  You can do this even if it's not your turn. */
12669
12670     if (appData.icsActive) {
12671         SendToICS(ics_prefix);
12672         SendToICS("resign\n");
12673     } else {
12674         switch (gameMode) {
12675           case MachinePlaysWhite:
12676             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12677             break;
12678           case MachinePlaysBlack:
12679             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12680             break;
12681           case EditGame:
12682             if (cmailMsgLoaded) {
12683                 TruncateGame();
12684                 if (WhiteOnMove(cmailOldMove)) {
12685                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12686                 } else {
12687                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12688                 }
12689                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12690             }
12691             break;
12692           default:
12693             break;
12694         }
12695     }
12696 }
12697
12698
12699 void
12700 StopObservingEvent()
12701 {
12702     /* Stop observing current games */
12703     SendToICS(ics_prefix);
12704     SendToICS("unobserve\n");
12705 }
12706
12707 void
12708 StopExaminingEvent()
12709 {
12710     /* Stop observing current game */
12711     SendToICS(ics_prefix);
12712     SendToICS("unexamine\n");
12713 }
12714
12715 void
12716 ForwardInner(target)
12717      int target;
12718 {
12719     int limit;
12720
12721     if (appData.debugMode)
12722         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12723                 target, currentMove, forwardMostMove);
12724
12725     if (gameMode == EditPosition)
12726       return;
12727
12728     if (gameMode == PlayFromGameFile && !pausing)
12729       PauseEvent();
12730
12731     if (gameMode == IcsExamining && pausing)
12732       limit = pauseExamForwardMostMove;
12733     else
12734       limit = forwardMostMove;
12735
12736     if (target > limit) target = limit;
12737
12738     if (target > 0 && moveList[target - 1][0]) {
12739         int fromX, fromY, toX, toY;
12740         toX = moveList[target - 1][2] - AAA;
12741         toY = moveList[target - 1][3] - ONE;
12742         if (moveList[target - 1][1] == '@') {
12743             if (appData.highlightLastMove) {
12744                 SetHighlights(-1, -1, toX, toY);
12745             }
12746         } else {
12747             fromX = moveList[target - 1][0] - AAA;
12748             fromY = moveList[target - 1][1] - ONE;
12749             if (target == currentMove + 1) {
12750                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12751             }
12752             if (appData.highlightLastMove) {
12753                 SetHighlights(fromX, fromY, toX, toY);
12754             }
12755         }
12756     }
12757     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12758         gameMode == Training || gameMode == PlayFromGameFile ||
12759         gameMode == AnalyzeFile) {
12760         while (currentMove < target) {
12761             SendMoveToProgram(currentMove++, &first);
12762         }
12763     } else {
12764         currentMove = target;
12765     }
12766
12767     if (gameMode == EditGame || gameMode == EndOfGame) {
12768         whiteTimeRemaining = timeRemaining[0][currentMove];
12769         blackTimeRemaining = timeRemaining[1][currentMove];
12770     }
12771     DisplayBothClocks();
12772     DisplayMove(currentMove - 1);
12773     DrawPosition(FALSE, boards[currentMove]);
12774     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12775     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12776         DisplayComment(currentMove - 1, commentList[currentMove]);
12777     }
12778 }
12779
12780
12781 void
12782 ForwardEvent()
12783 {
12784     if (gameMode == IcsExamining && !pausing) {
12785         SendToICS(ics_prefix);
12786         SendToICS("forward\n");
12787     } else {
12788         ForwardInner(currentMove + 1);
12789     }
12790 }
12791
12792 void
12793 ToEndEvent()
12794 {
12795     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12796         /* to optimze, we temporarily turn off analysis mode while we feed
12797          * the remaining moves to the engine. Otherwise we get analysis output
12798          * after each move.
12799          */
12800         if (first.analysisSupport) {
12801           SendToProgram("exit\nforce\n", &first);
12802           first.analyzing = FALSE;
12803         }
12804     }
12805
12806     if (gameMode == IcsExamining && !pausing) {
12807         SendToICS(ics_prefix);
12808         SendToICS("forward 999999\n");
12809     } else {
12810         ForwardInner(forwardMostMove);
12811     }
12812
12813     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12814         /* we have fed all the moves, so reactivate analysis mode */
12815         SendToProgram("analyze\n", &first);
12816         first.analyzing = TRUE;
12817         /*first.maybeThinking = TRUE;*/
12818         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12819     }
12820 }
12821
12822 void
12823 BackwardInner(target)
12824      int target;
12825 {
12826     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12827
12828     if (appData.debugMode)
12829         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12830                 target, currentMove, forwardMostMove);
12831
12832     if (gameMode == EditPosition) return;
12833     if (currentMove <= backwardMostMove) {
12834         ClearHighlights();
12835         DrawPosition(full_redraw, boards[currentMove]);
12836         return;
12837     }
12838     if (gameMode == PlayFromGameFile && !pausing)
12839       PauseEvent();
12840
12841     if (moveList[target][0]) {
12842         int fromX, fromY, toX, toY;
12843         toX = moveList[target][2] - AAA;
12844         toY = moveList[target][3] - ONE;
12845         if (moveList[target][1] == '@') {
12846             if (appData.highlightLastMove) {
12847                 SetHighlights(-1, -1, toX, toY);
12848             }
12849         } else {
12850             fromX = moveList[target][0] - AAA;
12851             fromY = moveList[target][1] - ONE;
12852             if (target == currentMove - 1) {
12853                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12854             }
12855             if (appData.highlightLastMove) {
12856                 SetHighlights(fromX, fromY, toX, toY);
12857             }
12858         }
12859     }
12860     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12861         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12862         while (currentMove > target) {
12863             SendToProgram("undo\n", &first);
12864             currentMove--;
12865         }
12866     } else {
12867         currentMove = target;
12868     }
12869
12870     if (gameMode == EditGame || gameMode == EndOfGame) {
12871         whiteTimeRemaining = timeRemaining[0][currentMove];
12872         blackTimeRemaining = timeRemaining[1][currentMove];
12873     }
12874     DisplayBothClocks();
12875     DisplayMove(currentMove - 1);
12876     DrawPosition(full_redraw, boards[currentMove]);
12877     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12878     // [HGM] PV info: routine tests if comment empty
12879     DisplayComment(currentMove - 1, commentList[currentMove]);
12880 }
12881
12882 void
12883 BackwardEvent()
12884 {
12885     if (gameMode == IcsExamining && !pausing) {
12886         SendToICS(ics_prefix);
12887         SendToICS("backward\n");
12888     } else {
12889         BackwardInner(currentMove - 1);
12890     }
12891 }
12892
12893 void
12894 ToStartEvent()
12895 {
12896     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12897         /* to optimize, we temporarily turn off analysis mode while we undo
12898          * all the moves. Otherwise we get analysis output after each undo.
12899          */
12900         if (first.analysisSupport) {
12901           SendToProgram("exit\nforce\n", &first);
12902           first.analyzing = FALSE;
12903         }
12904     }
12905
12906     if (gameMode == IcsExamining && !pausing) {
12907         SendToICS(ics_prefix);
12908         SendToICS("backward 999999\n");
12909     } else {
12910         BackwardInner(backwardMostMove);
12911     }
12912
12913     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12914         /* we have fed all the moves, so reactivate analysis mode */
12915         SendToProgram("analyze\n", &first);
12916         first.analyzing = TRUE;
12917         /*first.maybeThinking = TRUE;*/
12918         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12919     }
12920 }
12921
12922 void
12923 ToNrEvent(int to)
12924 {
12925   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12926   if (to >= forwardMostMove) to = forwardMostMove;
12927   if (to <= backwardMostMove) to = backwardMostMove;
12928   if (to < currentMove) {
12929     BackwardInner(to);
12930   } else {
12931     ForwardInner(to);
12932   }
12933 }
12934
12935 void
12936 RevertEvent(Boolean annotate)
12937 {
12938     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12939         return;
12940     }
12941     if (gameMode != IcsExamining) {
12942         DisplayError(_("You are not examining a game"), 0);
12943         return;
12944     }
12945     if (pausing) {
12946         DisplayError(_("You can't revert while pausing"), 0);
12947         return;
12948     }
12949     SendToICS(ics_prefix);
12950     SendToICS("revert\n");
12951 }
12952
12953 void
12954 RetractMoveEvent()
12955 {
12956     switch (gameMode) {
12957       case MachinePlaysWhite:
12958       case MachinePlaysBlack:
12959         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12960             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12961             return;
12962         }
12963         if (forwardMostMove < 2) return;
12964         currentMove = forwardMostMove = forwardMostMove - 2;
12965         whiteTimeRemaining = timeRemaining[0][currentMove];
12966         blackTimeRemaining = timeRemaining[1][currentMove];
12967         DisplayBothClocks();
12968         DisplayMove(currentMove - 1);
12969         ClearHighlights();/*!! could figure this out*/
12970         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12971         SendToProgram("remove\n", &first);
12972         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12973         break;
12974
12975       case BeginningOfGame:
12976       default:
12977         break;
12978
12979       case IcsPlayingWhite:
12980       case IcsPlayingBlack:
12981         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12982             SendToICS(ics_prefix);
12983             SendToICS("takeback 2\n");
12984         } else {
12985             SendToICS(ics_prefix);
12986             SendToICS("takeback 1\n");
12987         }
12988         break;
12989     }
12990 }
12991
12992 void
12993 MoveNowEvent()
12994 {
12995     ChessProgramState *cps;
12996
12997     switch (gameMode) {
12998       case MachinePlaysWhite:
12999         if (!WhiteOnMove(forwardMostMove)) {
13000             DisplayError(_("It is your turn"), 0);
13001             return;
13002         }
13003         cps = &first;
13004         break;
13005       case MachinePlaysBlack:
13006         if (WhiteOnMove(forwardMostMove)) {
13007             DisplayError(_("It is your turn"), 0);
13008             return;
13009         }
13010         cps = &first;
13011         break;
13012       case TwoMachinesPlay:
13013         if (WhiteOnMove(forwardMostMove) ==
13014             (first.twoMachinesColor[0] == 'w')) {
13015             cps = &first;
13016         } else {
13017             cps = &second;
13018         }
13019         break;
13020       case BeginningOfGame:
13021       default:
13022         return;
13023     }
13024     SendToProgram("?\n", cps);
13025 }
13026
13027 void
13028 TruncateGameEvent()
13029 {
13030     EditGameEvent();
13031     if (gameMode != EditGame) return;
13032     TruncateGame();
13033 }
13034
13035 void
13036 TruncateGame()
13037 {
13038     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13039     if (forwardMostMove > currentMove) {
13040         if (gameInfo.resultDetails != NULL) {
13041             free(gameInfo.resultDetails);
13042             gameInfo.resultDetails = NULL;
13043             gameInfo.result = GameUnfinished;
13044         }
13045         forwardMostMove = currentMove;
13046         HistorySet(parseList, backwardMostMove, forwardMostMove,
13047                    currentMove-1);
13048     }
13049 }
13050
13051 void
13052 HintEvent()
13053 {
13054     if (appData.noChessProgram) return;
13055     switch (gameMode) {
13056       case MachinePlaysWhite:
13057         if (WhiteOnMove(forwardMostMove)) {
13058             DisplayError(_("Wait until your turn"), 0);
13059             return;
13060         }
13061         break;
13062       case BeginningOfGame:
13063       case MachinePlaysBlack:
13064         if (!WhiteOnMove(forwardMostMove)) {
13065             DisplayError(_("Wait until your turn"), 0);
13066             return;
13067         }
13068         break;
13069       default:
13070         DisplayError(_("No hint available"), 0);
13071         return;
13072     }
13073     SendToProgram("hint\n", &first);
13074     hintRequested = TRUE;
13075 }
13076
13077 void
13078 BookEvent()
13079 {
13080     if (appData.noChessProgram) return;
13081     switch (gameMode) {
13082       case MachinePlaysWhite:
13083         if (WhiteOnMove(forwardMostMove)) {
13084             DisplayError(_("Wait until your turn"), 0);
13085             return;
13086         }
13087         break;
13088       case BeginningOfGame:
13089       case MachinePlaysBlack:
13090         if (!WhiteOnMove(forwardMostMove)) {
13091             DisplayError(_("Wait until your turn"), 0);
13092             return;
13093         }
13094         break;
13095       case EditPosition:
13096         EditPositionDone(TRUE);
13097         break;
13098       case TwoMachinesPlay:
13099         return;
13100       default:
13101         break;
13102     }
13103     SendToProgram("bk\n", &first);
13104     bookOutput[0] = NULLCHAR;
13105     bookRequested = TRUE;
13106 }
13107
13108 void
13109 AboutGameEvent()
13110 {
13111     char *tags = PGNTags(&gameInfo);
13112     TagsPopUp(tags, CmailMsg());
13113     free(tags);
13114 }
13115
13116 /* end button procedures */
13117
13118 void
13119 PrintPosition(fp, move)
13120      FILE *fp;
13121      int move;
13122 {
13123     int i, j;
13124
13125     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13126         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13127             char c = PieceToChar(boards[move][i][j]);
13128             fputc(c == 'x' ? '.' : c, fp);
13129             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13130         }
13131     }
13132     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13133       fprintf(fp, "white to play\n");
13134     else
13135       fprintf(fp, "black to play\n");
13136 }
13137
13138 void
13139 PrintOpponents(fp)
13140      FILE *fp;
13141 {
13142     if (gameInfo.white != NULL) {
13143         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13144     } else {
13145         fprintf(fp, "\n");
13146     }
13147 }
13148
13149 /* Find last component of program's own name, using some heuristics */
13150 void
13151 TidyProgramName(prog, host, buf)
13152      char *prog, *host, buf[MSG_SIZ];
13153 {
13154     char *p, *q;
13155     int local = (strcmp(host, "localhost") == 0);
13156     while (!local && (p = strchr(prog, ';')) != NULL) {
13157         p++;
13158         while (*p == ' ') p++;
13159         prog = p;
13160     }
13161     if (*prog == '"' || *prog == '\'') {
13162         q = strchr(prog + 1, *prog);
13163     } else {
13164         q = strchr(prog, ' ');
13165     }
13166     if (q == NULL) q = prog + strlen(prog);
13167     p = q;
13168     while (p >= prog && *p != '/' && *p != '\\') p--;
13169     p++;
13170     if(p == prog && *p == '"') p++;
13171     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13172     memcpy(buf, p, q - p);
13173     buf[q - p] = NULLCHAR;
13174     if (!local) {
13175         strcat(buf, "@");
13176         strcat(buf, host);
13177     }
13178 }
13179
13180 char *
13181 TimeControlTagValue()
13182 {
13183     char buf[MSG_SIZ];
13184     if (!appData.clockMode) {
13185       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13186     } else if (movesPerSession > 0) {
13187       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13188     } else if (timeIncrement == 0) {
13189       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13190     } else {
13191       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13192     }
13193     return StrSave(buf);
13194 }
13195
13196 void
13197 SetGameInfo()
13198 {
13199     /* This routine is used only for certain modes */
13200     VariantClass v = gameInfo.variant;
13201     ChessMove r = GameUnfinished;
13202     char *p = NULL;
13203
13204     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13205         r = gameInfo.result;
13206         p = gameInfo.resultDetails;
13207         gameInfo.resultDetails = NULL;
13208     }
13209     ClearGameInfo(&gameInfo);
13210     gameInfo.variant = v;
13211
13212     switch (gameMode) {
13213       case MachinePlaysWhite:
13214         gameInfo.event = StrSave( appData.pgnEventHeader );
13215         gameInfo.site = StrSave(HostName());
13216         gameInfo.date = PGNDate();
13217         gameInfo.round = StrSave("-");
13218         gameInfo.white = StrSave(first.tidy);
13219         gameInfo.black = StrSave(UserName());
13220         gameInfo.timeControl = TimeControlTagValue();
13221         break;
13222
13223       case MachinePlaysBlack:
13224         gameInfo.event = StrSave( appData.pgnEventHeader );
13225         gameInfo.site = StrSave(HostName());
13226         gameInfo.date = PGNDate();
13227         gameInfo.round = StrSave("-");
13228         gameInfo.white = StrSave(UserName());
13229         gameInfo.black = StrSave(first.tidy);
13230         gameInfo.timeControl = TimeControlTagValue();
13231         break;
13232
13233       case TwoMachinesPlay:
13234         gameInfo.event = StrSave( appData.pgnEventHeader );
13235         gameInfo.site = StrSave(HostName());
13236         gameInfo.date = PGNDate();
13237         if (matchGame > 0) {
13238             char buf[MSG_SIZ];
13239             snprintf(buf, MSG_SIZ, "%d", matchGame);
13240             gameInfo.round = StrSave(buf);
13241         } else {
13242             gameInfo.round = StrSave("-");
13243         }
13244         if (first.twoMachinesColor[0] == 'w') {
13245             gameInfo.white = StrSave(first.tidy);
13246             gameInfo.black = StrSave(second.tidy);
13247         } else {
13248             gameInfo.white = StrSave(second.tidy);
13249             gameInfo.black = StrSave(first.tidy);
13250         }
13251         gameInfo.timeControl = TimeControlTagValue();
13252         break;
13253
13254       case EditGame:
13255         gameInfo.event = StrSave("Edited game");
13256         gameInfo.site = StrSave(HostName());
13257         gameInfo.date = PGNDate();
13258         gameInfo.round = StrSave("-");
13259         gameInfo.white = StrSave("-");
13260         gameInfo.black = StrSave("-");
13261         gameInfo.result = r;
13262         gameInfo.resultDetails = p;
13263         break;
13264
13265       case EditPosition:
13266         gameInfo.event = StrSave("Edited position");
13267         gameInfo.site = StrSave(HostName());
13268         gameInfo.date = PGNDate();
13269         gameInfo.round = StrSave("-");
13270         gameInfo.white = StrSave("-");
13271         gameInfo.black = StrSave("-");
13272         break;
13273
13274       case IcsPlayingWhite:
13275       case IcsPlayingBlack:
13276       case IcsObserving:
13277       case IcsExamining:
13278         break;
13279
13280       case PlayFromGameFile:
13281         gameInfo.event = StrSave("Game from non-PGN file");
13282         gameInfo.site = StrSave(HostName());
13283         gameInfo.date = PGNDate();
13284         gameInfo.round = StrSave("-");
13285         gameInfo.white = StrSave("?");
13286         gameInfo.black = StrSave("?");
13287         break;
13288
13289       default:
13290         break;
13291     }
13292 }
13293
13294 void
13295 ReplaceComment(index, text)
13296      int index;
13297      char *text;
13298 {
13299     int len;
13300     char *p;
13301     float score;
13302
13303     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13304        pvInfoList[index-1].depth == len &&
13305        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13306        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13307     while (*text == '\n') text++;
13308     len = strlen(text);
13309     while (len > 0 && text[len - 1] == '\n') len--;
13310
13311     if (commentList[index] != NULL)
13312       free(commentList[index]);
13313
13314     if (len == 0) {
13315         commentList[index] = NULL;
13316         return;
13317     }
13318   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13319       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13320       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13321     commentList[index] = (char *) malloc(len + 2);
13322     strncpy(commentList[index], text, len);
13323     commentList[index][len] = '\n';
13324     commentList[index][len + 1] = NULLCHAR;
13325   } else {
13326     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13327     char *p;
13328     commentList[index] = (char *) malloc(len + 7);
13329     safeStrCpy(commentList[index], "{\n", 3);
13330     safeStrCpy(commentList[index]+2, text, len+1);
13331     commentList[index][len+2] = NULLCHAR;
13332     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13333     strcat(commentList[index], "\n}\n");
13334   }
13335 }
13336
13337 void
13338 CrushCRs(text)
13339      char *text;
13340 {
13341   char *p = text;
13342   char *q = text;
13343   char ch;
13344
13345   do {
13346     ch = *p++;
13347     if (ch == '\r') continue;
13348     *q++ = ch;
13349   } while (ch != '\0');
13350 }
13351
13352 void
13353 AppendComment(index, text, addBraces)
13354      int index;
13355      char *text;
13356      Boolean addBraces; // [HGM] braces: tells if we should add {}
13357 {
13358     int oldlen, len;
13359     char *old;
13360
13361 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13362     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13363
13364     CrushCRs(text);
13365     while (*text == '\n') text++;
13366     len = strlen(text);
13367     while (len > 0 && text[len - 1] == '\n') len--;
13368
13369     if (len == 0) return;
13370
13371     if (commentList[index] != NULL) {
13372         old = commentList[index];
13373         oldlen = strlen(old);
13374         while(commentList[index][oldlen-1] ==  '\n')
13375           commentList[index][--oldlen] = NULLCHAR;
13376         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13377         safeStrCpy(commentList[index], old, oldlen + len + 6);
13378         free(old);
13379         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13380         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13381           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13382           while (*text == '\n') { text++; len--; }
13383           commentList[index][--oldlen] = NULLCHAR;
13384       }
13385         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13386         else          strcat(commentList[index], "\n");
13387         strcat(commentList[index], text);
13388         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13389         else          strcat(commentList[index], "\n");
13390     } else {
13391         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13392         if(addBraces)
13393           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13394         else commentList[index][0] = NULLCHAR;
13395         strcat(commentList[index], text);
13396         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13397         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13398     }
13399 }
13400
13401 static char * FindStr( char * text, char * sub_text )
13402 {
13403     char * result = strstr( text, sub_text );
13404
13405     if( result != NULL ) {
13406         result += strlen( sub_text );
13407     }
13408
13409     return result;
13410 }
13411
13412 /* [AS] Try to extract PV info from PGN comment */
13413 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13414 char *GetInfoFromComment( int index, char * text )
13415 {
13416     char * sep = text, *p;
13417
13418     if( text != NULL && index > 0 ) {
13419         int score = 0;
13420         int depth = 0;
13421         int time = -1, sec = 0, deci;
13422         char * s_eval = FindStr( text, "[%eval " );
13423         char * s_emt = FindStr( text, "[%emt " );
13424
13425         if( s_eval != NULL || s_emt != NULL ) {
13426             /* New style */
13427             char delim;
13428
13429             if( s_eval != NULL ) {
13430                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13431                     return text;
13432                 }
13433
13434                 if( delim != ']' ) {
13435                     return text;
13436                 }
13437             }
13438
13439             if( s_emt != NULL ) {
13440             }
13441                 return text;
13442         }
13443         else {
13444             /* We expect something like: [+|-]nnn.nn/dd */
13445             int score_lo = 0;
13446
13447             if(*text != '{') return text; // [HGM] braces: must be normal comment
13448
13449             sep = strchr( text, '/' );
13450             if( sep == NULL || sep < (text+4) ) {
13451                 return text;
13452             }
13453
13454             p = text;
13455             if(p[1] == '(') { // comment starts with PV
13456                p = strchr(p, ')'); // locate end of PV
13457                if(p == NULL || sep < p+5) return text;
13458                // at this point we have something like "{(.*) +0.23/6 ..."
13459                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13460                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13461                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13462             }
13463             time = -1; sec = -1; deci = -1;
13464             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13465                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13466                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13467                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13468                 return text;
13469             }
13470
13471             if( score_lo < 0 || score_lo >= 100 ) {
13472                 return text;
13473             }
13474
13475             if(sec >= 0) time = 600*time + 10*sec; else
13476             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13477
13478             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13479
13480             /* [HGM] PV time: now locate end of PV info */
13481             while( *++sep >= '0' && *sep <= '9'); // strip depth
13482             if(time >= 0)
13483             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13484             if(sec >= 0)
13485             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13486             if(deci >= 0)
13487             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13488             while(*sep == ' ') sep++;
13489         }
13490
13491         if( depth <= 0 ) {
13492             return text;
13493         }
13494
13495         if( time < 0 ) {
13496             time = -1;
13497         }
13498
13499         pvInfoList[index-1].depth = depth;
13500         pvInfoList[index-1].score = score;
13501         pvInfoList[index-1].time  = 10*time; // centi-sec
13502         if(*sep == '}') *sep = 0; else *--sep = '{';
13503         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13504     }
13505     return sep;
13506 }
13507
13508 void
13509 SendToProgram(message, cps)
13510      char *message;
13511      ChessProgramState *cps;
13512 {
13513     int count, outCount, error;
13514     char buf[MSG_SIZ];
13515
13516     if (cps->pr == NULL) return;
13517     Attention(cps);
13518
13519     if (appData.debugMode) {
13520         TimeMark now;
13521         GetTimeMark(&now);
13522         fprintf(debugFP, "%ld >%-6s: %s",
13523                 SubtractTimeMarks(&now, &programStartTime),
13524                 cps->which, message);
13525     }
13526
13527     count = strlen(message);
13528     outCount = OutputToProcess(cps->pr, message, count, &error);
13529     if (outCount < count && !exiting
13530                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13531       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13532         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13533             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13534                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13535                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13536             } else {
13537                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13538             }
13539             gameInfo.resultDetails = StrSave(buf);
13540         }
13541         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13542     }
13543 }
13544
13545 void
13546 ReceiveFromProgram(isr, closure, message, count, error)
13547      InputSourceRef isr;
13548      VOIDSTAR closure;
13549      char *message;
13550      int count;
13551      int error;
13552 {
13553     char *end_str;
13554     char buf[MSG_SIZ];
13555     ChessProgramState *cps = (ChessProgramState *)closure;
13556
13557     if (isr != cps->isr) return; /* Killed intentionally */
13558     if (count <= 0) {
13559         if (count == 0) {
13560             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13561                     cps->which, cps->program);
13562         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13563                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13564                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13565                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13566                 } else {
13567                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13568                 }
13569                 gameInfo.resultDetails = StrSave(buf);
13570             }
13571             RemoveInputSource(cps->isr);
13572             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13573         } else {
13574             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13575                     cps->which, cps->program);
13576             RemoveInputSource(cps->isr);
13577
13578             /* [AS] Program is misbehaving badly... kill it */
13579             if( count == -2 ) {
13580                 DestroyChildProcess( cps->pr, 9 );
13581                 cps->pr = NoProc;
13582             }
13583
13584             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13585         }
13586         return;
13587     }
13588
13589     if ((end_str = strchr(message, '\r')) != NULL)
13590       *end_str = NULLCHAR;
13591     if ((end_str = strchr(message, '\n')) != NULL)
13592       *end_str = NULLCHAR;
13593
13594     if (appData.debugMode) {
13595         TimeMark now; int print = 1;
13596         char *quote = ""; char c; int i;
13597
13598         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13599                 char start = message[0];
13600                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13601                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13602                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13603                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13604                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13605                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13606                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13607                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13608                    sscanf(message, "hint: %c", &c)!=1 && 
13609                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13610                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13611                     print = (appData.engineComments >= 2);
13612                 }
13613                 message[0] = start; // restore original message
13614         }
13615         if(print) {
13616                 GetTimeMark(&now);
13617                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13618                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13619                         quote,
13620                         message);
13621         }
13622     }
13623
13624     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13625     if (appData.icsEngineAnalyze) {
13626         if (strstr(message, "whisper") != NULL ||
13627              strstr(message, "kibitz") != NULL ||
13628             strstr(message, "tellics") != NULL) return;
13629     }
13630
13631     HandleMachineMove(message, cps);
13632 }
13633
13634
13635 void
13636 SendTimeControl(cps, mps, tc, inc, sd, st)
13637      ChessProgramState *cps;
13638      int mps, inc, sd, st;
13639      long tc;
13640 {
13641     char buf[MSG_SIZ];
13642     int seconds;
13643
13644     if( timeControl_2 > 0 ) {
13645         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13646             tc = timeControl_2;
13647         }
13648     }
13649     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13650     inc /= cps->timeOdds;
13651     st  /= cps->timeOdds;
13652
13653     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13654
13655     if (st > 0) {
13656       /* Set exact time per move, normally using st command */
13657       if (cps->stKludge) {
13658         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13659         seconds = st % 60;
13660         if (seconds == 0) {
13661           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13662         } else {
13663           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13664         }
13665       } else {
13666         snprintf(buf, MSG_SIZ, "st %d\n", st);
13667       }
13668     } else {
13669       /* Set conventional or incremental time control, using level command */
13670       if (seconds == 0) {
13671         /* Note old gnuchess bug -- minutes:seconds used to not work.
13672            Fixed in later versions, but still avoid :seconds
13673            when seconds is 0. */
13674         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13675       } else {
13676         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13677                  seconds, inc/1000.);
13678       }
13679     }
13680     SendToProgram(buf, cps);
13681
13682     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13683     /* Orthogonally, limit search to given depth */
13684     if (sd > 0) {
13685       if (cps->sdKludge) {
13686         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13687       } else {
13688         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13689       }
13690       SendToProgram(buf, cps);
13691     }
13692
13693     if(cps->nps > 0) { /* [HGM] nps */
13694         if(cps->supportsNPS == FALSE)
13695           cps->nps = -1; // don't use if engine explicitly says not supported!
13696         else {
13697           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13698           SendToProgram(buf, cps);
13699         }
13700     }
13701 }
13702
13703 ChessProgramState *WhitePlayer()
13704 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13705 {
13706     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13707        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13708         return &second;
13709     return &first;
13710 }
13711
13712 void
13713 SendTimeRemaining(cps, machineWhite)
13714      ChessProgramState *cps;
13715      int /*boolean*/ machineWhite;
13716 {
13717     char message[MSG_SIZ];
13718     long time, otime;
13719
13720     /* Note: this routine must be called when the clocks are stopped
13721        or when they have *just* been set or switched; otherwise
13722        it will be off by the time since the current tick started.
13723     */
13724     if (machineWhite) {
13725         time = whiteTimeRemaining / 10;
13726         otime = blackTimeRemaining / 10;
13727     } else {
13728         time = blackTimeRemaining / 10;
13729         otime = whiteTimeRemaining / 10;
13730     }
13731     /* [HGM] translate opponent's time by time-odds factor */
13732     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13733     if (appData.debugMode) {
13734         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13735     }
13736
13737     if (time <= 0) time = 1;
13738     if (otime <= 0) otime = 1;
13739
13740     snprintf(message, MSG_SIZ, "time %ld\n", time);
13741     SendToProgram(message, cps);
13742
13743     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13744     SendToProgram(message, cps);
13745 }
13746
13747 int
13748 BoolFeature(p, name, loc, cps)
13749      char **p;
13750      char *name;
13751      int *loc;
13752      ChessProgramState *cps;
13753 {
13754   char buf[MSG_SIZ];
13755   int len = strlen(name);
13756   int val;
13757
13758   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13759     (*p) += len + 1;
13760     sscanf(*p, "%d", &val);
13761     *loc = (val != 0);
13762     while (**p && **p != ' ')
13763       (*p)++;
13764     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13765     SendToProgram(buf, cps);
13766     return TRUE;
13767   }
13768   return FALSE;
13769 }
13770
13771 int
13772 IntFeature(p, name, loc, cps)
13773      char **p;
13774      char *name;
13775      int *loc;
13776      ChessProgramState *cps;
13777 {
13778   char buf[MSG_SIZ];
13779   int len = strlen(name);
13780   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13781     (*p) += len + 1;
13782     sscanf(*p, "%d", loc);
13783     while (**p && **p != ' ') (*p)++;
13784     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13785     SendToProgram(buf, cps);
13786     return TRUE;
13787   }
13788   return FALSE;
13789 }
13790
13791 int
13792 StringFeature(p, name, loc, cps)
13793      char **p;
13794      char *name;
13795      char loc[];
13796      ChessProgramState *cps;
13797 {
13798   char buf[MSG_SIZ];
13799   int len = strlen(name);
13800   if (strncmp((*p), name, len) == 0
13801       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13802     (*p) += len + 2;
13803     sscanf(*p, "%[^\"]", loc);
13804     while (**p && **p != '\"') (*p)++;
13805     if (**p == '\"') (*p)++;
13806     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13807     SendToProgram(buf, cps);
13808     return TRUE;
13809   }
13810   return FALSE;
13811 }
13812
13813 int
13814 ParseOption(Option *opt, ChessProgramState *cps)
13815 // [HGM] options: process the string that defines an engine option, and determine
13816 // name, type, default value, and allowed value range
13817 {
13818         char *p, *q, buf[MSG_SIZ];
13819         int n, min = (-1)<<31, max = 1<<31, def;
13820
13821         if(p = strstr(opt->name, " -spin ")) {
13822             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13823             if(max < min) max = min; // enforce consistency
13824             if(def < min) def = min;
13825             if(def > max) def = max;
13826             opt->value = def;
13827             opt->min = min;
13828             opt->max = max;
13829             opt->type = Spin;
13830         } else if((p = strstr(opt->name, " -slider "))) {
13831             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13832             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13833             if(max < min) max = min; // enforce consistency
13834             if(def < min) def = min;
13835             if(def > max) def = max;
13836             opt->value = def;
13837             opt->min = min;
13838             opt->max = max;
13839             opt->type = Spin; // Slider;
13840         } else if((p = strstr(opt->name, " -string "))) {
13841             opt->textValue = p+9;
13842             opt->type = TextBox;
13843         } else if((p = strstr(opt->name, " -file "))) {
13844             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13845             opt->textValue = p+7;
13846             opt->type = TextBox; // FileName;
13847         } else if((p = strstr(opt->name, " -path "))) {
13848             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13849             opt->textValue = p+7;
13850             opt->type = TextBox; // PathName;
13851         } else if(p = strstr(opt->name, " -check ")) {
13852             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13853             opt->value = (def != 0);
13854             opt->type = CheckBox;
13855         } else if(p = strstr(opt->name, " -combo ")) {
13856             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13857             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13858             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13859             opt->value = n = 0;
13860             while(q = StrStr(q, " /// ")) {
13861                 n++; *q = 0;    // count choices, and null-terminate each of them
13862                 q += 5;
13863                 if(*q == '*') { // remember default, which is marked with * prefix
13864                     q++;
13865                     opt->value = n;
13866                 }
13867                 cps->comboList[cps->comboCnt++] = q;
13868             }
13869             cps->comboList[cps->comboCnt++] = NULL;
13870             opt->max = n + 1;
13871             opt->type = ComboBox;
13872         } else if(p = strstr(opt->name, " -button")) {
13873             opt->type = Button;
13874         } else if(p = strstr(opt->name, " -save")) {
13875             opt->type = SaveButton;
13876         } else return FALSE;
13877         *p = 0; // terminate option name
13878         // now look if the command-line options define a setting for this engine option.
13879         if(cps->optionSettings && cps->optionSettings[0])
13880             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13881         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13882           snprintf(buf, MSG_SIZ, "option %s", p);
13883                 if(p = strstr(buf, ",")) *p = 0;
13884                 if(q = strchr(buf, '=')) switch(opt->type) {
13885                     case ComboBox:
13886                         for(n=0; n<opt->max; n++)
13887                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13888                         break;
13889                     case TextBox:
13890                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13891                         break;
13892                     case Spin:
13893                     case CheckBox:
13894                         opt->value = atoi(q+1);
13895                     default:
13896                         break;
13897                 }
13898                 strcat(buf, "\n");
13899                 SendToProgram(buf, cps);
13900         }
13901         return TRUE;
13902 }
13903
13904 void
13905 FeatureDone(cps, val)
13906      ChessProgramState* cps;
13907      int val;
13908 {
13909   DelayedEventCallback cb = GetDelayedEvent();
13910   if ((cb == InitBackEnd3 && cps == &first) ||
13911       (cb == SettingsMenuIfReady && cps == &second) ||
13912       (cb == TwoMachinesEventIfReady && cps == &second)) {
13913     CancelDelayedEvent();
13914     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13915   }
13916   cps->initDone = val;
13917 }
13918
13919 /* Parse feature command from engine */
13920 void
13921 ParseFeatures(args, cps)
13922      char* args;
13923      ChessProgramState *cps;
13924 {
13925   char *p = args;
13926   char *q;
13927   int val;
13928   char buf[MSG_SIZ];
13929
13930   for (;;) {
13931     while (*p == ' ') p++;
13932     if (*p == NULLCHAR) return;
13933
13934     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13935     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13936     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13937     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13938     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13939     if (BoolFeature(&p, "reuse", &val, cps)) {
13940       /* Engine can disable reuse, but can't enable it if user said no */
13941       if (!val) cps->reuse = FALSE;
13942       continue;
13943     }
13944     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13945     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13946       if (gameMode == TwoMachinesPlay) {
13947         DisplayTwoMachinesTitle();
13948       } else {
13949         DisplayTitle("");
13950       }
13951       continue;
13952     }
13953     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13954     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13955     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13956     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13957     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13958     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13959     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13960     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13961     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13962     if (IntFeature(&p, "done", &val, cps)) {
13963       FeatureDone(cps, val);
13964       continue;
13965     }
13966     /* Added by Tord: */
13967     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13968     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13969     /* End of additions by Tord */
13970
13971     /* [HGM] added features: */
13972     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13973     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13974     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13975     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13976     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13977     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13978     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13979         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13980           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13981             SendToProgram(buf, cps);
13982             continue;
13983         }
13984         if(cps->nrOptions >= MAX_OPTIONS) {
13985             cps->nrOptions--;
13986             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13987             DisplayError(buf, 0);
13988         }
13989         continue;
13990     }
13991     /* End of additions by HGM */
13992
13993     /* unknown feature: complain and skip */
13994     q = p;
13995     while (*q && *q != '=') q++;
13996     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13997     SendToProgram(buf, cps);
13998     p = q;
13999     if (*p == '=') {
14000       p++;
14001       if (*p == '\"') {
14002         p++;
14003         while (*p && *p != '\"') p++;
14004         if (*p == '\"') p++;
14005       } else {
14006         while (*p && *p != ' ') p++;
14007       }
14008     }
14009   }
14010
14011 }
14012
14013 void
14014 PeriodicUpdatesEvent(newState)
14015      int newState;
14016 {
14017     if (newState == appData.periodicUpdates)
14018       return;
14019
14020     appData.periodicUpdates=newState;
14021
14022     /* Display type changes, so update it now */
14023 //    DisplayAnalysis();
14024
14025     /* Get the ball rolling again... */
14026     if (newState) {
14027         AnalysisPeriodicEvent(1);
14028         StartAnalysisClock();
14029     }
14030 }
14031
14032 void
14033 PonderNextMoveEvent(newState)
14034      int newState;
14035 {
14036     if (newState == appData.ponderNextMove) return;
14037     if (gameMode == EditPosition) EditPositionDone(TRUE);
14038     if (newState) {
14039         SendToProgram("hard\n", &first);
14040         if (gameMode == TwoMachinesPlay) {
14041             SendToProgram("hard\n", &second);
14042         }
14043     } else {
14044         SendToProgram("easy\n", &first);
14045         thinkOutput[0] = NULLCHAR;
14046         if (gameMode == TwoMachinesPlay) {
14047             SendToProgram("easy\n", &second);
14048         }
14049     }
14050     appData.ponderNextMove = newState;
14051 }
14052
14053 void
14054 NewSettingEvent(option, feature, command, value)
14055      char *command;
14056      int option, value, *feature;
14057 {
14058     char buf[MSG_SIZ];
14059
14060     if (gameMode == EditPosition) EditPositionDone(TRUE);
14061     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14062     if(feature == NULL || *feature) SendToProgram(buf, &first);
14063     if (gameMode == TwoMachinesPlay) {
14064         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14065     }
14066 }
14067
14068 void
14069 ShowThinkingEvent()
14070 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14071 {
14072     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14073     int newState = appData.showThinking
14074         // [HGM] thinking: other features now need thinking output as well
14075         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14076
14077     if (oldState == newState) return;
14078     oldState = newState;
14079     if (gameMode == EditPosition) EditPositionDone(TRUE);
14080     if (oldState) {
14081         SendToProgram("post\n", &first);
14082         if (gameMode == TwoMachinesPlay) {
14083             SendToProgram("post\n", &second);
14084         }
14085     } else {
14086         SendToProgram("nopost\n", &first);
14087         thinkOutput[0] = NULLCHAR;
14088         if (gameMode == TwoMachinesPlay) {
14089             SendToProgram("nopost\n", &second);
14090         }
14091     }
14092 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14093 }
14094
14095 void
14096 AskQuestionEvent(title, question, replyPrefix, which)
14097      char *title; char *question; char *replyPrefix; char *which;
14098 {
14099   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14100   if (pr == NoProc) return;
14101   AskQuestion(title, question, replyPrefix, pr);
14102 }
14103
14104 void
14105 DisplayMove(moveNumber)
14106      int moveNumber;
14107 {
14108     char message[MSG_SIZ];
14109     char res[MSG_SIZ];
14110     char cpThinkOutput[MSG_SIZ];
14111
14112     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14113
14114     if (moveNumber == forwardMostMove - 1 ||
14115         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14116
14117         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14118
14119         if (strchr(cpThinkOutput, '\n')) {
14120             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14121         }
14122     } else {
14123         *cpThinkOutput = NULLCHAR;
14124     }
14125
14126     /* [AS] Hide thinking from human user */
14127     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14128         *cpThinkOutput = NULLCHAR;
14129         if( thinkOutput[0] != NULLCHAR ) {
14130             int i;
14131
14132             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14133                 cpThinkOutput[i] = '.';
14134             }
14135             cpThinkOutput[i] = NULLCHAR;
14136             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14137         }
14138     }
14139
14140     if (moveNumber == forwardMostMove - 1 &&
14141         gameInfo.resultDetails != NULL) {
14142         if (gameInfo.resultDetails[0] == NULLCHAR) {
14143           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14144         } else {
14145           snprintf(res, MSG_SIZ, " {%s} %s",
14146                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14147         }
14148     } else {
14149         res[0] = NULLCHAR;
14150     }
14151
14152     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14153         DisplayMessage(res, cpThinkOutput);
14154     } else {
14155       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14156                 WhiteOnMove(moveNumber) ? " " : ".. ",
14157                 parseList[moveNumber], res);
14158         DisplayMessage(message, cpThinkOutput);
14159     }
14160 }
14161
14162 void
14163 DisplayComment(moveNumber, text)
14164      int moveNumber;
14165      char *text;
14166 {
14167     char title[MSG_SIZ];
14168     char buf[8000]; // comment can be long!
14169     int score, depth;
14170
14171     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14172       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14173     } else {
14174       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14175               WhiteOnMove(moveNumber) ? " " : ".. ",
14176               parseList[moveNumber]);
14177     }
14178     // [HGM] PV info: display PV info together with (or as) comment
14179     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14180       if(text == NULL) text = "";
14181       score = pvInfoList[moveNumber].score;
14182       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14183               depth, (pvInfoList[moveNumber].time+50)/100, text);
14184       text = buf;
14185     }
14186     if (text != NULL && (appData.autoDisplayComment || commentUp))
14187         CommentPopUp(title, text);
14188 }
14189
14190 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14191  * might be busy thinking or pondering.  It can be omitted if your
14192  * gnuchess is configured to stop thinking immediately on any user
14193  * input.  However, that gnuchess feature depends on the FIONREAD
14194  * ioctl, which does not work properly on some flavors of Unix.
14195  */
14196 void
14197 Attention(cps)
14198      ChessProgramState *cps;
14199 {
14200 #if ATTENTION
14201     if (!cps->useSigint) return;
14202     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14203     switch (gameMode) {
14204       case MachinePlaysWhite:
14205       case MachinePlaysBlack:
14206       case TwoMachinesPlay:
14207       case IcsPlayingWhite:
14208       case IcsPlayingBlack:
14209       case AnalyzeMode:
14210       case AnalyzeFile:
14211         /* Skip if we know it isn't thinking */
14212         if (!cps->maybeThinking) return;
14213         if (appData.debugMode)
14214           fprintf(debugFP, "Interrupting %s\n", cps->which);
14215         InterruptChildProcess(cps->pr);
14216         cps->maybeThinking = FALSE;
14217         break;
14218       default:
14219         break;
14220     }
14221 #endif /*ATTENTION*/
14222 }
14223
14224 int
14225 CheckFlags()
14226 {
14227     if (whiteTimeRemaining <= 0) {
14228         if (!whiteFlag) {
14229             whiteFlag = TRUE;
14230             if (appData.icsActive) {
14231                 if (appData.autoCallFlag &&
14232                     gameMode == IcsPlayingBlack && !blackFlag) {
14233                   SendToICS(ics_prefix);
14234                   SendToICS("flag\n");
14235                 }
14236             } else {
14237                 if (blackFlag) {
14238                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14239                 } else {
14240                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14241                     if (appData.autoCallFlag) {
14242                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14243                         return TRUE;
14244                     }
14245                 }
14246             }
14247         }
14248     }
14249     if (blackTimeRemaining <= 0) {
14250         if (!blackFlag) {
14251             blackFlag = TRUE;
14252             if (appData.icsActive) {
14253                 if (appData.autoCallFlag &&
14254                     gameMode == IcsPlayingWhite && !whiteFlag) {
14255                   SendToICS(ics_prefix);
14256                   SendToICS("flag\n");
14257                 }
14258             } else {
14259                 if (whiteFlag) {
14260                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14261                 } else {
14262                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14263                     if (appData.autoCallFlag) {
14264                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14265                         return TRUE;
14266                     }
14267                 }
14268             }
14269         }
14270     }
14271     return FALSE;
14272 }
14273
14274 void
14275 CheckTimeControl()
14276 {
14277     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14278         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14279
14280     /*
14281      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14282      */
14283     if ( !WhiteOnMove(forwardMostMove) ) {
14284         /* White made time control */
14285         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14286         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14287         /* [HGM] time odds: correct new time quota for time odds! */
14288                                             / WhitePlayer()->timeOdds;
14289         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14290     } else {
14291         lastBlack -= blackTimeRemaining;
14292         /* Black made time control */
14293         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14294                                             / WhitePlayer()->other->timeOdds;
14295         lastWhite = whiteTimeRemaining;
14296     }
14297 }
14298
14299 void
14300 DisplayBothClocks()
14301 {
14302     int wom = gameMode == EditPosition ?
14303       !blackPlaysFirst : WhiteOnMove(currentMove);
14304     DisplayWhiteClock(whiteTimeRemaining, wom);
14305     DisplayBlackClock(blackTimeRemaining, !wom);
14306 }
14307
14308
14309 /* Timekeeping seems to be a portability nightmare.  I think everyone
14310    has ftime(), but I'm really not sure, so I'm including some ifdefs
14311    to use other calls if you don't.  Clocks will be less accurate if
14312    you have neither ftime nor gettimeofday.
14313 */
14314
14315 /* VS 2008 requires the #include outside of the function */
14316 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14317 #include <sys/timeb.h>
14318 #endif
14319
14320 /* Get the current time as a TimeMark */
14321 void
14322 GetTimeMark(tm)
14323      TimeMark *tm;
14324 {
14325 #if HAVE_GETTIMEOFDAY
14326
14327     struct timeval timeVal;
14328     struct timezone timeZone;
14329
14330     gettimeofday(&timeVal, &timeZone);
14331     tm->sec = (long) timeVal.tv_sec;
14332     tm->ms = (int) (timeVal.tv_usec / 1000L);
14333
14334 #else /*!HAVE_GETTIMEOFDAY*/
14335 #if HAVE_FTIME
14336
14337 // include <sys/timeb.h> / moved to just above start of function
14338     struct timeb timeB;
14339
14340     ftime(&timeB);
14341     tm->sec = (long) timeB.time;
14342     tm->ms = (int) timeB.millitm;
14343
14344 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14345     tm->sec = (long) time(NULL);
14346     tm->ms = 0;
14347 #endif
14348 #endif
14349 }
14350
14351 /* Return the difference in milliseconds between two
14352    time marks.  We assume the difference will fit in a long!
14353 */
14354 long
14355 SubtractTimeMarks(tm2, tm1)
14356      TimeMark *tm2, *tm1;
14357 {
14358     return 1000L*(tm2->sec - tm1->sec) +
14359            (long) (tm2->ms - tm1->ms);
14360 }
14361
14362
14363 /*
14364  * Code to manage the game clocks.
14365  *
14366  * In tournament play, black starts the clock and then white makes a move.
14367  * We give the human user a slight advantage if he is playing white---the
14368  * clocks don't run until he makes his first move, so it takes zero time.
14369  * Also, we don't account for network lag, so we could get out of sync
14370  * with GNU Chess's clock -- but then, referees are always right.
14371  */
14372
14373 static TimeMark tickStartTM;
14374 static long intendedTickLength;
14375
14376 long
14377 NextTickLength(timeRemaining)
14378      long timeRemaining;
14379 {
14380     long nominalTickLength, nextTickLength;
14381
14382     if (timeRemaining > 0L && timeRemaining <= 10000L)
14383       nominalTickLength = 100L;
14384     else
14385       nominalTickLength = 1000L;
14386     nextTickLength = timeRemaining % nominalTickLength;
14387     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14388
14389     return nextTickLength;
14390 }
14391
14392 /* Adjust clock one minute up or down */
14393 void
14394 AdjustClock(Boolean which, int dir)
14395 {
14396     if(which) blackTimeRemaining += 60000*dir;
14397     else      whiteTimeRemaining += 60000*dir;
14398     DisplayBothClocks();
14399 }
14400
14401 /* Stop clocks and reset to a fresh time control */
14402 void
14403 ResetClocks()
14404 {
14405     (void) StopClockTimer();
14406     if (appData.icsActive) {
14407         whiteTimeRemaining = blackTimeRemaining = 0;
14408     } else if (searchTime) {
14409         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14410         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14411     } else { /* [HGM] correct new time quote for time odds */
14412         whiteTC = blackTC = fullTimeControlString;
14413         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14414         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14415     }
14416     if (whiteFlag || blackFlag) {
14417         DisplayTitle("");
14418         whiteFlag = blackFlag = FALSE;
14419     }
14420     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14421     DisplayBothClocks();
14422 }
14423
14424 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14425
14426 /* Decrement running clock by amount of time that has passed */
14427 void
14428 DecrementClocks()
14429 {
14430     long timeRemaining;
14431     long lastTickLength, fudge;
14432     TimeMark now;
14433
14434     if (!appData.clockMode) return;
14435     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14436
14437     GetTimeMark(&now);
14438
14439     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14440
14441     /* Fudge if we woke up a little too soon */
14442     fudge = intendedTickLength - lastTickLength;
14443     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14444
14445     if (WhiteOnMove(forwardMostMove)) {
14446         if(whiteNPS >= 0) lastTickLength = 0;
14447         timeRemaining = whiteTimeRemaining -= lastTickLength;
14448         if(timeRemaining < 0 && !appData.icsActive) {
14449             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14450             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14451                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14452                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14453             }
14454         }
14455         DisplayWhiteClock(whiteTimeRemaining - fudge,
14456                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14457     } else {
14458         if(blackNPS >= 0) lastTickLength = 0;
14459         timeRemaining = blackTimeRemaining -= lastTickLength;
14460         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14461             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14462             if(suddenDeath) {
14463                 blackStartMove = forwardMostMove;
14464                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14465             }
14466         }
14467         DisplayBlackClock(blackTimeRemaining - fudge,
14468                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14469     }
14470     if (CheckFlags()) return;
14471
14472     tickStartTM = now;
14473     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14474     StartClockTimer(intendedTickLength);
14475
14476     /* if the time remaining has fallen below the alarm threshold, sound the
14477      * alarm. if the alarm has sounded and (due to a takeback or time control
14478      * with increment) the time remaining has increased to a level above the
14479      * threshold, reset the alarm so it can sound again.
14480      */
14481
14482     if (appData.icsActive && appData.icsAlarm) {
14483
14484         /* make sure we are dealing with the user's clock */
14485         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14486                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14487            )) return;
14488
14489         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14490             alarmSounded = FALSE;
14491         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14492             PlayAlarmSound();
14493             alarmSounded = TRUE;
14494         }
14495     }
14496 }
14497
14498
14499 /* A player has just moved, so stop the previously running
14500    clock and (if in clock mode) start the other one.
14501    We redisplay both clocks in case we're in ICS mode, because
14502    ICS gives us an update to both clocks after every move.
14503    Note that this routine is called *after* forwardMostMove
14504    is updated, so the last fractional tick must be subtracted
14505    from the color that is *not* on move now.
14506 */
14507 void
14508 SwitchClocks(int newMoveNr)
14509 {
14510     long lastTickLength;
14511     TimeMark now;
14512     int flagged = FALSE;
14513
14514     GetTimeMark(&now);
14515
14516     if (StopClockTimer() && appData.clockMode) {
14517         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14518         if (!WhiteOnMove(forwardMostMove)) {
14519             if(blackNPS >= 0) lastTickLength = 0;
14520             blackTimeRemaining -= lastTickLength;
14521            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14522 //         if(pvInfoList[forwardMostMove-1].time == -1)
14523                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14524                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14525         } else {
14526            if(whiteNPS >= 0) lastTickLength = 0;
14527            whiteTimeRemaining -= lastTickLength;
14528            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14529 //         if(pvInfoList[forwardMostMove-1].time == -1)
14530                  pvInfoList[forwardMostMove-1].time =
14531                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14532         }
14533         flagged = CheckFlags();
14534     }
14535     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14536     CheckTimeControl();
14537
14538     if (flagged || !appData.clockMode) return;
14539
14540     switch (gameMode) {
14541       case MachinePlaysBlack:
14542       case MachinePlaysWhite:
14543       case BeginningOfGame:
14544         if (pausing) return;
14545         break;
14546
14547       case EditGame:
14548       case PlayFromGameFile:
14549       case IcsExamining:
14550         return;
14551
14552       default:
14553         break;
14554     }
14555
14556     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14557         if(WhiteOnMove(forwardMostMove))
14558              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14559         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14560     }
14561
14562     tickStartTM = now;
14563     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14564       whiteTimeRemaining : blackTimeRemaining);
14565     StartClockTimer(intendedTickLength);
14566 }
14567
14568
14569 /* Stop both clocks */
14570 void
14571 StopClocks()
14572 {
14573     long lastTickLength;
14574     TimeMark now;
14575
14576     if (!StopClockTimer()) return;
14577     if (!appData.clockMode) return;
14578
14579     GetTimeMark(&now);
14580
14581     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14582     if (WhiteOnMove(forwardMostMove)) {
14583         if(whiteNPS >= 0) lastTickLength = 0;
14584         whiteTimeRemaining -= lastTickLength;
14585         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14586     } else {
14587         if(blackNPS >= 0) lastTickLength = 0;
14588         blackTimeRemaining -= lastTickLength;
14589         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14590     }
14591     CheckFlags();
14592 }
14593
14594 /* Start clock of player on move.  Time may have been reset, so
14595    if clock is already running, stop and restart it. */
14596 void
14597 StartClocks()
14598 {
14599     (void) StopClockTimer(); /* in case it was running already */
14600     DisplayBothClocks();
14601     if (CheckFlags()) return;
14602
14603     if (!appData.clockMode) return;
14604     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14605
14606     GetTimeMark(&tickStartTM);
14607     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14608       whiteTimeRemaining : blackTimeRemaining);
14609
14610    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14611     whiteNPS = blackNPS = -1;
14612     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14613        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14614         whiteNPS = first.nps;
14615     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14616        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14617         blackNPS = first.nps;
14618     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14619         whiteNPS = second.nps;
14620     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14621         blackNPS = second.nps;
14622     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14623
14624     StartClockTimer(intendedTickLength);
14625 }
14626
14627 char *
14628 TimeString(ms)
14629      long ms;
14630 {
14631     long second, minute, hour, day;
14632     char *sign = "";
14633     static char buf[32];
14634
14635     if (ms > 0 && ms <= 9900) {
14636       /* convert milliseconds to tenths, rounding up */
14637       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14638
14639       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14640       return buf;
14641     }
14642
14643     /* convert milliseconds to seconds, rounding up */
14644     /* use floating point to avoid strangeness of integer division
14645        with negative dividends on many machines */
14646     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14647
14648     if (second < 0) {
14649         sign = "-";
14650         second = -second;
14651     }
14652
14653     day = second / (60 * 60 * 24);
14654     second = second % (60 * 60 * 24);
14655     hour = second / (60 * 60);
14656     second = second % (60 * 60);
14657     minute = second / 60;
14658     second = second % 60;
14659
14660     if (day > 0)
14661       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14662               sign, day, hour, minute, second);
14663     else if (hour > 0)
14664       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14665     else
14666       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14667
14668     return buf;
14669 }
14670
14671
14672 /*
14673  * This is necessary because some C libraries aren't ANSI C compliant yet.
14674  */
14675 char *
14676 StrStr(string, match)
14677      char *string, *match;
14678 {
14679     int i, length;
14680
14681     length = strlen(match);
14682
14683     for (i = strlen(string) - length; i >= 0; i--, string++)
14684       if (!strncmp(match, string, length))
14685         return string;
14686
14687     return NULL;
14688 }
14689
14690 char *
14691 StrCaseStr(string, match)
14692      char *string, *match;
14693 {
14694     int i, j, length;
14695
14696     length = strlen(match);
14697
14698     for (i = strlen(string) - length; i >= 0; i--, string++) {
14699         for (j = 0; j < length; j++) {
14700             if (ToLower(match[j]) != ToLower(string[j]))
14701               break;
14702         }
14703         if (j == length) return string;
14704     }
14705
14706     return NULL;
14707 }
14708
14709 #ifndef _amigados
14710 int
14711 StrCaseCmp(s1, s2)
14712      char *s1, *s2;
14713 {
14714     char c1, c2;
14715
14716     for (;;) {
14717         c1 = ToLower(*s1++);
14718         c2 = ToLower(*s2++);
14719         if (c1 > c2) return 1;
14720         if (c1 < c2) return -1;
14721         if (c1 == NULLCHAR) return 0;
14722     }
14723 }
14724
14725
14726 int
14727 ToLower(c)
14728      int c;
14729 {
14730     return isupper(c) ? tolower(c) : c;
14731 }
14732
14733
14734 int
14735 ToUpper(c)
14736      int c;
14737 {
14738     return islower(c) ? toupper(c) : c;
14739 }
14740 #endif /* !_amigados    */
14741
14742 char *
14743 StrSave(s)
14744      char *s;
14745 {
14746   char *ret;
14747
14748   if ((ret = (char *) malloc(strlen(s) + 1)))
14749     {
14750       safeStrCpy(ret, s, strlen(s)+1);
14751     }
14752   return ret;
14753 }
14754
14755 char *
14756 StrSavePtr(s, savePtr)
14757      char *s, **savePtr;
14758 {
14759     if (*savePtr) {
14760         free(*savePtr);
14761     }
14762     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14763       safeStrCpy(*savePtr, s, strlen(s)+1);
14764     }
14765     return(*savePtr);
14766 }
14767
14768 char *
14769 PGNDate()
14770 {
14771     time_t clock;
14772     struct tm *tm;
14773     char buf[MSG_SIZ];
14774
14775     clock = time((time_t *)NULL);
14776     tm = localtime(&clock);
14777     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14778             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14779     return StrSave(buf);
14780 }
14781
14782
14783 char *
14784 PositionToFEN(move, overrideCastling)
14785      int move;
14786      char *overrideCastling;
14787 {
14788     int i, j, fromX, fromY, toX, toY;
14789     int whiteToPlay;
14790     char buf[128];
14791     char *p, *q;
14792     int emptycount;
14793     ChessSquare piece;
14794
14795     whiteToPlay = (gameMode == EditPosition) ?
14796       !blackPlaysFirst : (move % 2 == 0);
14797     p = buf;
14798
14799     /* Piece placement data */
14800     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14801         emptycount = 0;
14802         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14803             if (boards[move][i][j] == EmptySquare) {
14804                 emptycount++;
14805             } else { ChessSquare piece = boards[move][i][j];
14806                 if (emptycount > 0) {
14807                     if(emptycount<10) /* [HGM] can be >= 10 */
14808                         *p++ = '0' + emptycount;
14809                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14810                     emptycount = 0;
14811                 }
14812                 if(PieceToChar(piece) == '+') {
14813                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14814                     *p++ = '+';
14815                     piece = (ChessSquare)(DEMOTED piece);
14816                 }
14817                 *p++ = PieceToChar(piece);
14818                 if(p[-1] == '~') {
14819                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14820                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14821                     *p++ = '~';
14822                 }
14823             }
14824         }
14825         if (emptycount > 0) {
14826             if(emptycount<10) /* [HGM] can be >= 10 */
14827                 *p++ = '0' + emptycount;
14828             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14829             emptycount = 0;
14830         }
14831         *p++ = '/';
14832     }
14833     *(p - 1) = ' ';
14834
14835     /* [HGM] print Crazyhouse or Shogi holdings */
14836     if( gameInfo.holdingsWidth ) {
14837         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14838         q = p;
14839         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14840             piece = boards[move][i][BOARD_WIDTH-1];
14841             if( piece != EmptySquare )
14842               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14843                   *p++ = PieceToChar(piece);
14844         }
14845         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14846             piece = boards[move][BOARD_HEIGHT-i-1][0];
14847             if( piece != EmptySquare )
14848               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14849                   *p++ = PieceToChar(piece);
14850         }
14851
14852         if( q == p ) *p++ = '-';
14853         *p++ = ']';
14854         *p++ = ' ';
14855     }
14856
14857     /* Active color */
14858     *p++ = whiteToPlay ? 'w' : 'b';
14859     *p++ = ' ';
14860
14861   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14862     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14863   } else {
14864   if(nrCastlingRights) {
14865      q = p;
14866      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14867        /* [HGM] write directly from rights */
14868            if(boards[move][CASTLING][2] != NoRights &&
14869               boards[move][CASTLING][0] != NoRights   )
14870                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14871            if(boards[move][CASTLING][2] != NoRights &&
14872               boards[move][CASTLING][1] != NoRights   )
14873                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14874            if(boards[move][CASTLING][5] != NoRights &&
14875               boards[move][CASTLING][3] != NoRights   )
14876                 *p++ = boards[move][CASTLING][3] + AAA;
14877            if(boards[move][CASTLING][5] != NoRights &&
14878               boards[move][CASTLING][4] != NoRights   )
14879                 *p++ = boards[move][CASTLING][4] + AAA;
14880      } else {
14881
14882         /* [HGM] write true castling rights */
14883         if( nrCastlingRights == 6 ) {
14884             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14885                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14886             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14887                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14888             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14889                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14890             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14891                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14892         }
14893      }
14894      if (q == p) *p++ = '-'; /* No castling rights */
14895      *p++ = ' ';
14896   }
14897
14898   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14899      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14900     /* En passant target square */
14901     if (move > backwardMostMove) {
14902         fromX = moveList[move - 1][0] - AAA;
14903         fromY = moveList[move - 1][1] - ONE;
14904         toX = moveList[move - 1][2] - AAA;
14905         toY = moveList[move - 1][3] - ONE;
14906         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14907             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14908             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14909             fromX == toX) {
14910             /* 2-square pawn move just happened */
14911             *p++ = toX + AAA;
14912             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14913         } else {
14914             *p++ = '-';
14915         }
14916     } else if(move == backwardMostMove) {
14917         // [HGM] perhaps we should always do it like this, and forget the above?
14918         if((signed char)boards[move][EP_STATUS] >= 0) {
14919             *p++ = boards[move][EP_STATUS] + AAA;
14920             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14921         } else {
14922             *p++ = '-';
14923         }
14924     } else {
14925         *p++ = '-';
14926     }
14927     *p++ = ' ';
14928   }
14929   }
14930
14931     /* [HGM] find reversible plies */
14932     {   int i = 0, j=move;
14933
14934         if (appData.debugMode) { int k;
14935             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14936             for(k=backwardMostMove; k<=forwardMostMove; k++)
14937                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14938
14939         }
14940
14941         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14942         if( j == backwardMostMove ) i += initialRulePlies;
14943         sprintf(p, "%d ", i);
14944         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14945     }
14946     /* Fullmove number */
14947     sprintf(p, "%d", (move / 2) + 1);
14948
14949     return StrSave(buf);
14950 }
14951
14952 Boolean
14953 ParseFEN(board, blackPlaysFirst, fen)
14954     Board board;
14955      int *blackPlaysFirst;
14956      char *fen;
14957 {
14958     int i, j;
14959     char *p, c;
14960     int emptycount;
14961     ChessSquare piece;
14962
14963     p = fen;
14964
14965     /* [HGM] by default clear Crazyhouse holdings, if present */
14966     if(gameInfo.holdingsWidth) {
14967        for(i=0; i<BOARD_HEIGHT; i++) {
14968            board[i][0]             = EmptySquare; /* black holdings */
14969            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14970            board[i][1]             = (ChessSquare) 0; /* black counts */
14971            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14972        }
14973     }
14974
14975     /* Piece placement data */
14976     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14977         j = 0;
14978         for (;;) {
14979             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14980                 if (*p == '/') p++;
14981                 emptycount = gameInfo.boardWidth - j;
14982                 while (emptycount--)
14983                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14984                 break;
14985 #if(BOARD_FILES >= 10)
14986             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14987                 p++; emptycount=10;
14988                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14989                 while (emptycount--)
14990                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14991 #endif
14992             } else if (isdigit(*p)) {
14993                 emptycount = *p++ - '0';
14994                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14995                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14996                 while (emptycount--)
14997                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14998             } else if (*p == '+' || isalpha(*p)) {
14999                 if (j >= gameInfo.boardWidth) return FALSE;
15000                 if(*p=='+') {
15001                     piece = CharToPiece(*++p);
15002                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15003                     piece = (ChessSquare) (PROMOTED piece ); p++;
15004                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15005                 } else piece = CharToPiece(*p++);
15006
15007                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15008                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15009                     piece = (ChessSquare) (PROMOTED piece);
15010                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15011                     p++;
15012                 }
15013                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15014             } else {
15015                 return FALSE;
15016             }
15017         }
15018     }
15019     while (*p == '/' || *p == ' ') p++;
15020
15021     /* [HGM] look for Crazyhouse holdings here */
15022     while(*p==' ') p++;
15023     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15024         if(*p == '[') p++;
15025         if(*p == '-' ) p++; /* empty holdings */ else {
15026             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15027             /* if we would allow FEN reading to set board size, we would   */
15028             /* have to add holdings and shift the board read so far here   */
15029             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15030                 p++;
15031                 if((int) piece >= (int) BlackPawn ) {
15032                     i = (int)piece - (int)BlackPawn;
15033                     i = PieceToNumber((ChessSquare)i);
15034                     if( i >= gameInfo.holdingsSize ) return FALSE;
15035                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15036                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15037                 } else {
15038                     i = (int)piece - (int)WhitePawn;
15039                     i = PieceToNumber((ChessSquare)i);
15040                     if( i >= gameInfo.holdingsSize ) return FALSE;
15041                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15042                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15043                 }
15044             }
15045         }
15046         if(*p == ']') p++;
15047     }
15048
15049     while(*p == ' ') p++;
15050
15051     /* Active color */
15052     c = *p++;
15053     if(appData.colorNickNames) {
15054       if( c == appData.colorNickNames[0] ) c = 'w'; else
15055       if( c == appData.colorNickNames[1] ) c = 'b';
15056     }
15057     switch (c) {
15058       case 'w':
15059         *blackPlaysFirst = FALSE;
15060         break;
15061       case 'b':
15062         *blackPlaysFirst = TRUE;
15063         break;
15064       default:
15065         return FALSE;
15066     }
15067
15068     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15069     /* return the extra info in global variiables             */
15070
15071     /* set defaults in case FEN is incomplete */
15072     board[EP_STATUS] = EP_UNKNOWN;
15073     for(i=0; i<nrCastlingRights; i++ ) {
15074         board[CASTLING][i] =
15075             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15076     }   /* assume possible unless obviously impossible */
15077     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15078     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15079     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15080                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15081     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15082     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15083     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15084                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15085     FENrulePlies = 0;
15086
15087     while(*p==' ') p++;
15088     if(nrCastlingRights) {
15089       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15090           /* castling indicator present, so default becomes no castlings */
15091           for(i=0; i<nrCastlingRights; i++ ) {
15092                  board[CASTLING][i] = NoRights;
15093           }
15094       }
15095       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15096              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15097              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15098              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15099         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15100
15101         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15102             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15103             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15104         }
15105         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15106             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15107         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15108                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15109         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15110                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15111         switch(c) {
15112           case'K':
15113               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15114               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15115               board[CASTLING][2] = whiteKingFile;
15116               break;
15117           case'Q':
15118               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15119               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15120               board[CASTLING][2] = whiteKingFile;
15121               break;
15122           case'k':
15123               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15124               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15125               board[CASTLING][5] = blackKingFile;
15126               break;
15127           case'q':
15128               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15129               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15130               board[CASTLING][5] = blackKingFile;
15131           case '-':
15132               break;
15133           default: /* FRC castlings */
15134               if(c >= 'a') { /* black rights */
15135                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15136                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15137                   if(i == BOARD_RGHT) break;
15138                   board[CASTLING][5] = i;
15139                   c -= AAA;
15140                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15141                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15142                   if(c > i)
15143                       board[CASTLING][3] = c;
15144                   else
15145                       board[CASTLING][4] = c;
15146               } else { /* white rights */
15147                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15148                     if(board[0][i] == WhiteKing) break;
15149                   if(i == BOARD_RGHT) break;
15150                   board[CASTLING][2] = i;
15151                   c -= AAA - 'a' + 'A';
15152                   if(board[0][c] >= WhiteKing) break;
15153                   if(c > i)
15154                       board[CASTLING][0] = c;
15155                   else
15156                       board[CASTLING][1] = c;
15157               }
15158         }
15159       }
15160       for(i=0; i<nrCastlingRights; i++)
15161         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15162     if (appData.debugMode) {
15163         fprintf(debugFP, "FEN castling rights:");
15164         for(i=0; i<nrCastlingRights; i++)
15165         fprintf(debugFP, " %d", board[CASTLING][i]);
15166         fprintf(debugFP, "\n");
15167     }
15168
15169       while(*p==' ') p++;
15170     }
15171
15172     /* read e.p. field in games that know e.p. capture */
15173     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15174        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15175       if(*p=='-') {
15176         p++; board[EP_STATUS] = EP_NONE;
15177       } else {
15178          char c = *p++ - AAA;
15179
15180          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15181          if(*p >= '0' && *p <='9') p++;
15182          board[EP_STATUS] = c;
15183       }
15184     }
15185
15186
15187     if(sscanf(p, "%d", &i) == 1) {
15188         FENrulePlies = i; /* 50-move ply counter */
15189         /* (The move number is still ignored)    */
15190     }
15191
15192     return TRUE;
15193 }
15194
15195 void
15196 EditPositionPasteFEN(char *fen)
15197 {
15198   if (fen != NULL) {
15199     Board initial_position;
15200
15201     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15202       DisplayError(_("Bad FEN position in clipboard"), 0);
15203       return ;
15204     } else {
15205       int savedBlackPlaysFirst = blackPlaysFirst;
15206       EditPositionEvent();
15207       blackPlaysFirst = savedBlackPlaysFirst;
15208       CopyBoard(boards[0], initial_position);
15209       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15210       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15211       DisplayBothClocks();
15212       DrawPosition(FALSE, boards[currentMove]);
15213     }
15214   }
15215 }
15216
15217 static char cseq[12] = "\\   ";
15218
15219 Boolean set_cont_sequence(char *new_seq)
15220 {
15221     int len;
15222     Boolean ret;
15223
15224     // handle bad attempts to set the sequence
15225         if (!new_seq)
15226                 return 0; // acceptable error - no debug
15227
15228     len = strlen(new_seq);
15229     ret = (len > 0) && (len < sizeof(cseq));
15230     if (ret)
15231       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15232     else if (appData.debugMode)
15233       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15234     return ret;
15235 }
15236
15237 /*
15238     reformat a source message so words don't cross the width boundary.  internal
15239     newlines are not removed.  returns the wrapped size (no null character unless
15240     included in source message).  If dest is NULL, only calculate the size required
15241     for the dest buffer.  lp argument indicats line position upon entry, and it's
15242     passed back upon exit.
15243 */
15244 int wrap(char *dest, char *src, int count, int width, int *lp)
15245 {
15246     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15247
15248     cseq_len = strlen(cseq);
15249     old_line = line = *lp;
15250     ansi = len = clen = 0;
15251
15252     for (i=0; i < count; i++)
15253     {
15254         if (src[i] == '\033')
15255             ansi = 1;
15256
15257         // if we hit the width, back up
15258         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15259         {
15260             // store i & len in case the word is too long
15261             old_i = i, old_len = len;
15262
15263             // find the end of the last word
15264             while (i && src[i] != ' ' && src[i] != '\n')
15265             {
15266                 i--;
15267                 len--;
15268             }
15269
15270             // word too long?  restore i & len before splitting it
15271             if ((old_i-i+clen) >= width)
15272             {
15273                 i = old_i;
15274                 len = old_len;
15275             }
15276
15277             // extra space?
15278             if (i && src[i-1] == ' ')
15279                 len--;
15280
15281             if (src[i] != ' ' && src[i] != '\n')
15282             {
15283                 i--;
15284                 if (len)
15285                     len--;
15286             }
15287
15288             // now append the newline and continuation sequence
15289             if (dest)
15290                 dest[len] = '\n';
15291             len++;
15292             if (dest)
15293                 strncpy(dest+len, cseq, cseq_len);
15294             len += cseq_len;
15295             line = cseq_len;
15296             clen = cseq_len;
15297             continue;
15298         }
15299
15300         if (dest)
15301             dest[len] = src[i];
15302         len++;
15303         if (!ansi)
15304             line++;
15305         if (src[i] == '\n')
15306             line = 0;
15307         if (src[i] == 'm')
15308             ansi = 0;
15309     }
15310     if (dest && appData.debugMode)
15311     {
15312         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15313             count, width, line, len, *lp);
15314         show_bytes(debugFP, src, count);
15315         fprintf(debugFP, "\ndest: ");
15316         show_bytes(debugFP, dest, len);
15317         fprintf(debugFP, "\n");
15318     }
15319     *lp = dest ? line : old_line;
15320
15321     return len;
15322 }
15323
15324 // [HGM] vari: routines for shelving variations
15325
15326 void
15327 PushTail(int firstMove, int lastMove)
15328 {
15329         int i, j, nrMoves = lastMove - firstMove;
15330
15331         if(appData.icsActive) { // only in local mode
15332                 forwardMostMove = currentMove; // mimic old ICS behavior
15333                 return;
15334         }
15335         if(storedGames >= MAX_VARIATIONS-1) return;
15336
15337         // push current tail of game on stack
15338         savedResult[storedGames] = gameInfo.result;
15339         savedDetails[storedGames] = gameInfo.resultDetails;
15340         gameInfo.resultDetails = NULL;
15341         savedFirst[storedGames] = firstMove;
15342         savedLast [storedGames] = lastMove;
15343         savedFramePtr[storedGames] = framePtr;
15344         framePtr -= nrMoves; // reserve space for the boards
15345         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15346             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15347             for(j=0; j<MOVE_LEN; j++)
15348                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15349             for(j=0; j<2*MOVE_LEN; j++)
15350                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15351             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15352             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15353             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15354             pvInfoList[firstMove+i-1].depth = 0;
15355             commentList[framePtr+i] = commentList[firstMove+i];
15356             commentList[firstMove+i] = NULL;
15357         }
15358
15359         storedGames++;
15360         forwardMostMove = firstMove; // truncate game so we can start variation
15361         if(storedGames == 1) GreyRevert(FALSE);
15362 }
15363
15364 Boolean
15365 PopTail(Boolean annotate)
15366 {
15367         int i, j, nrMoves;
15368         char buf[8000], moveBuf[20];
15369
15370         if(appData.icsActive) return FALSE; // only in local mode
15371         if(!storedGames) return FALSE; // sanity
15372         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15373
15374         storedGames--;
15375         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15376         nrMoves = savedLast[storedGames] - currentMove;
15377         if(annotate) {
15378                 int cnt = 10;
15379                 if(!WhiteOnMove(currentMove))
15380                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15381                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15382                 for(i=currentMove; i<forwardMostMove; i++) {
15383                         if(WhiteOnMove(i))
15384                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15385                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15386                         strcat(buf, moveBuf);
15387                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15388                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15389                 }
15390                 strcat(buf, ")");
15391         }
15392         for(i=1; i<=nrMoves; i++) { // copy last variation back
15393             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15394             for(j=0; j<MOVE_LEN; j++)
15395                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15396             for(j=0; j<2*MOVE_LEN; j++)
15397                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15398             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15399             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15400             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15401             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15402             commentList[currentMove+i] = commentList[framePtr+i];
15403             commentList[framePtr+i] = NULL;
15404         }
15405         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15406         framePtr = savedFramePtr[storedGames];
15407         gameInfo.result = savedResult[storedGames];
15408         if(gameInfo.resultDetails != NULL) {
15409             free(gameInfo.resultDetails);
15410       }
15411         gameInfo.resultDetails = savedDetails[storedGames];
15412         forwardMostMove = currentMove + nrMoves;
15413         if(storedGames == 0) GreyRevert(TRUE);
15414         return TRUE;
15415 }
15416
15417 void
15418 CleanupTail()
15419 {       // remove all shelved variations
15420         int i;
15421         for(i=0; i<storedGames; i++) {
15422             if(savedDetails[i])
15423                 free(savedDetails[i]);
15424             savedDetails[i] = NULL;
15425         }
15426         for(i=framePtr; i<MAX_MOVES; i++) {
15427                 if(commentList[i]) free(commentList[i]);
15428                 commentList[i] = NULL;
15429         }
15430         framePtr = MAX_MOVES-1;
15431         storedGames = 0;
15432 }
15433
15434 void
15435 LoadVariation(int index, char *text)
15436 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15437         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15438         int level = 0, move;
15439
15440         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15441         // first find outermost bracketing variation
15442         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15443             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15444                 if(*p == '{') wait = '}'; else
15445                 if(*p == '[') wait = ']'; else
15446                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15447                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15448             }
15449             if(*p == wait) wait = NULLCHAR; // closing ]} found
15450             p++;
15451         }
15452         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15453         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15454         end[1] = NULLCHAR; // clip off comment beyond variation
15455         ToNrEvent(currentMove-1);
15456         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15457         // kludge: use ParsePV() to append variation to game
15458         move = currentMove;
15459         ParsePV(start, TRUE);
15460         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15461         ClearPremoveHighlights();
15462         CommentPopDown();
15463         ToNrEvent(currentMove+1);
15464 }
15465