02cc45e9562d62f74ce8fbc2710d860bab63d3c8
[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, 2011 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 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy( char *dst, const char *src, size_t count )
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble(u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags(index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating(str)
647   char *str;
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine(ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions(ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737 "first",
738 "second"
739 };
740
741 void
742 InitEngine(ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine(ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     appData.seedBase = random() + (random()<<15);
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len >= MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len >= MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for draw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantGrand:      /* should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void EscapeExpand(char *p, char *q)
1718 {       // [HGM] initstring: routine to shape up string arguments
1719         while(*p++ = *q++) if(p[-1] == '\\')
1720             switch(*q++) {
1721                 case 'n': p[-1] = '\n'; break;
1722                 case 'r': p[-1] = '\r'; break;
1723                 case 't': p[-1] = '\t'; break;
1724                 case '\\': p[-1] = '\\'; break;
1725                 case 0: *p = 0; return;
1726                 default: p[-1] = q[-1]; break;
1727             }
1728 }
1729
1730 void
1731 show_bytes(fp, buf, count)
1732      FILE *fp;
1733      char *buf;
1734      int count;
1735 {
1736     while (count--) {
1737         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1738             fprintf(fp, "\\%03o", *buf & 0xff);
1739         } else {
1740             putc(*buf, fp);
1741         }
1742         buf++;
1743     }
1744     fflush(fp);
1745 }
1746
1747 /* Returns an errno value */
1748 int
1749 OutputMaybeTelnet(pr, message, count, outError)
1750      ProcRef pr;
1751      char *message;
1752      int count;
1753      int *outError;
1754 {
1755     char buf[8192], *p, *q, *buflim;
1756     int left, newcount, outcount;
1757
1758     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1759         *appData.gateway != NULLCHAR) {
1760         if (appData.debugMode) {
1761             fprintf(debugFP, ">ICS: ");
1762             show_bytes(debugFP, message, count);
1763             fprintf(debugFP, "\n");
1764         }
1765         return OutputToProcess(pr, message, count, outError);
1766     }
1767
1768     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769     p = message;
1770     q = buf;
1771     left = count;
1772     newcount = 0;
1773     while (left) {
1774         if (q >= buflim) {
1775             if (appData.debugMode) {
1776                 fprintf(debugFP, ">ICS: ");
1777                 show_bytes(debugFP, buf, newcount);
1778                 fprintf(debugFP, "\n");
1779             }
1780             outcount = OutputToProcess(pr, buf, newcount, outError);
1781             if (outcount < newcount) return -1; /* to be sure */
1782             q = buf;
1783             newcount = 0;
1784         }
1785         if (*p == '\n') {
1786             *q++ = '\r';
1787             newcount++;
1788         } else if (((unsigned char) *p) == TN_IAC) {
1789             *q++ = (char) TN_IAC;
1790             newcount ++;
1791         }
1792         *q++ = *p++;
1793         newcount++;
1794         left--;
1795     }
1796     if (appData.debugMode) {
1797         fprintf(debugFP, ">ICS: ");
1798         show_bytes(debugFP, buf, newcount);
1799         fprintf(debugFP, "\n");
1800     }
1801     outcount = OutputToProcess(pr, buf, newcount, outError);
1802     if (outcount < newcount) return -1; /* to be sure */
1803     return count;
1804 }
1805
1806 void
1807 read_from_player(isr, closure, message, count, error)
1808      InputSourceRef isr;
1809      VOIDSTAR closure;
1810      char *message;
1811      int count;
1812      int error;
1813 {
1814     int outError, outCount;
1815     static int gotEof = 0;
1816
1817     /* Pass data read from player on to ICS */
1818     if (count > 0) {
1819         gotEof = 0;
1820         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1821         if (outCount < count) {
1822             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1823         }
1824     } else if (count < 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1827     } else if (gotEof++ > 0) {
1828         RemoveInputSource(isr);
1829         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1830     }
1831 }
1832
1833 void
1834 KeepAlive()
1835 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1836     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1837     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1838     SendToICS("date\n");
1839     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1840 }
1841
1842 /* added routine for printf style output to ics */
1843 void ics_printf(char *format, ...)
1844 {
1845     char buffer[MSG_SIZ];
1846     va_list args;
1847
1848     va_start(args, format);
1849     vsnprintf(buffer, sizeof(buffer), format, args);
1850     buffer[sizeof(buffer)-1] = '\0';
1851     SendToICS(buffer);
1852     va_end(args);
1853 }
1854
1855 void
1856 SendToICS(s)
1857      char *s;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NoProc) return;
1862
1863     count = strlen(s);
1864     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1865     if (outCount < count) {
1866         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867     }
1868 }
1869
1870 /* This is used for sending logon scripts to the ICS. Sending
1871    without a delay causes problems when using timestamp on ICC
1872    (at least on my machine). */
1873 void
1874 SendToICSDelayed(s,msdelay)
1875      char *s;
1876      long msdelay;
1877 {
1878     int count, outCount, outError;
1879
1880     if (icsPR == NoProc) return;
1881
1882     count = strlen(s);
1883     if (appData.debugMode) {
1884         fprintf(debugFP, ">ICS: ");
1885         show_bytes(debugFP, s, count);
1886         fprintf(debugFP, "\n");
1887     }
1888     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1889                                       msdelay);
1890     if (outCount < count) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895
1896 /* Remove all highlighting escape sequences in s
1897    Also deletes any suffix starting with '('
1898    */
1899 char *
1900 StripHighlightAndTitle(s)
1901      char *s;
1902 {
1903     static char retbuf[MSG_SIZ];
1904     char *p = retbuf;
1905
1906     while (*s != NULLCHAR) {
1907         while (*s == '\033') {
1908             while (*s != NULLCHAR && !isalpha(*s)) s++;
1909             if (*s != NULLCHAR) s++;
1910         }
1911         while (*s != NULLCHAR && *s != '\033') {
1912             if (*s == '(' || *s == '[') {
1913                 *p = NULLCHAR;
1914                 return retbuf;
1915             }
1916             *p++ = *s++;
1917         }
1918     }
1919     *p = NULLCHAR;
1920     return retbuf;
1921 }
1922
1923 /* Remove all highlighting escape sequences in s */
1924 char *
1925 StripHighlight(s)
1926      char *s;
1927 {
1928     static char retbuf[MSG_SIZ];
1929     char *p = retbuf;
1930
1931     while (*s != NULLCHAR) {
1932         while (*s == '\033') {
1933             while (*s != NULLCHAR && !isalpha(*s)) s++;
1934             if (*s != NULLCHAR) s++;
1935         }
1936         while (*s != NULLCHAR && *s != '\033') {
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 char *variantNames[] = VARIANT_NAMES;
1945 char *
1946 VariantName(v)
1947      VariantClass v;
1948 {
1949     return variantNames[v];
1950 }
1951
1952
1953 /* Identify a variant from the strings the chess servers use or the
1954    PGN Variant tag names we use. */
1955 VariantClass
1956 StringToVariant(e)
1957      char *e;
1958 {
1959     char *p;
1960     int wnum = -1;
1961     VariantClass v = VariantNormal;
1962     int i, found = FALSE;
1963     char buf[MSG_SIZ];
1964     int len;
1965
1966     if (!e) return v;
1967
1968     /* [HGM] skip over optional board-size prefixes */
1969     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1970         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1971         while( *e++ != '_');
1972     }
1973
1974     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1975         v = VariantNormal;
1976         found = TRUE;
1977     } else
1978     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1979       if (StrCaseStr(e, variantNames[i])) {
1980         v = (VariantClass) i;
1981         found = TRUE;
1982         break;
1983       }
1984     }
1985
1986     if (!found) {
1987       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1988           || StrCaseStr(e, "wild/fr")
1989           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1990         v = VariantFischeRandom;
1991       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1992                  (i = 1, p = StrCaseStr(e, "w"))) {
1993         p += i;
1994         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1995         if (isdigit(*p)) {
1996           wnum = atoi(p);
1997         } else {
1998           wnum = -1;
1999         }
2000         switch (wnum) {
2001         case 0: /* FICS only, actually */
2002         case 1:
2003           /* Castling legal even if K starts on d-file */
2004           v = VariantWildCastle;
2005           break;
2006         case 2:
2007         case 3:
2008         case 4:
2009           /* Castling illegal even if K & R happen to start in
2010              normal positions. */
2011           v = VariantNoCastle;
2012           break;
2013         case 5:
2014         case 7:
2015         case 8:
2016         case 10:
2017         case 11:
2018         case 12:
2019         case 13:
2020         case 14:
2021         case 15:
2022         case 18:
2023         case 19:
2024           /* Castling legal iff K & R start in normal positions */
2025           v = VariantNormal;
2026           break;
2027         case 6:
2028         case 20:
2029         case 21:
2030           /* Special wilds for position setup; unclear what to do here */
2031           v = VariantLoadable;
2032           break;
2033         case 9:
2034           /* Bizarre ICC game */
2035           v = VariantTwoKings;
2036           break;
2037         case 16:
2038           v = VariantKriegspiel;
2039           break;
2040         case 17:
2041           v = VariantLosers;
2042           break;
2043         case 22:
2044           v = VariantFischeRandom;
2045           break;
2046         case 23:
2047           v = VariantCrazyhouse;
2048           break;
2049         case 24:
2050           v = VariantBughouse;
2051           break;
2052         case 25:
2053           v = Variant3Check;
2054           break;
2055         case 26:
2056           /* Not quite the same as FICS suicide! */
2057           v = VariantGiveaway;
2058           break;
2059         case 27:
2060           v = VariantAtomic;
2061           break;
2062         case 28:
2063           v = VariantShatranj;
2064           break;
2065
2066         /* Temporary names for future ICC types.  The name *will* change in
2067            the next xboard/WinBoard release after ICC defines it. */
2068         case 29:
2069           v = Variant29;
2070           break;
2071         case 30:
2072           v = Variant30;
2073           break;
2074         case 31:
2075           v = Variant31;
2076           break;
2077         case 32:
2078           v = Variant32;
2079           break;
2080         case 33:
2081           v = Variant33;
2082           break;
2083         case 34:
2084           v = Variant34;
2085           break;
2086         case 35:
2087           v = Variant35;
2088           break;
2089         case 36:
2090           v = Variant36;
2091           break;
2092         case 37:
2093           v = VariantShogi;
2094           break;
2095         case 38:
2096           v = VariantXiangqi;
2097           break;
2098         case 39:
2099           v = VariantCourier;
2100           break;
2101         case 40:
2102           v = VariantGothic;
2103           break;
2104         case 41:
2105           v = VariantCapablanca;
2106           break;
2107         case 42:
2108           v = VariantKnightmate;
2109           break;
2110         case 43:
2111           v = VariantFairy;
2112           break;
2113         case 44:
2114           v = VariantCylinder;
2115           break;
2116         case 45:
2117           v = VariantFalcon;
2118           break;
2119         case 46:
2120           v = VariantCapaRandom;
2121           break;
2122         case 47:
2123           v = VariantBerolina;
2124           break;
2125         case 48:
2126           v = VariantJanus;
2127           break;
2128         case 49:
2129           v = VariantSuper;
2130           break;
2131         case 50:
2132           v = VariantGreat;
2133           break;
2134         case -1:
2135           /* Found "wild" or "w" in the string but no number;
2136              must assume it's normal chess. */
2137           v = VariantNormal;
2138           break;
2139         default:
2140           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2141           if( (len >= MSG_SIZ) && appData.debugMode )
2142             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2143
2144           DisplayError(buf, 0);
2145           v = VariantUnknown;
2146           break;
2147         }
2148       }
2149     }
2150     if (appData.debugMode) {
2151       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2152               e, wnum, VariantName(v));
2153     }
2154     return v;
2155 }
2156
2157 static int leftover_start = 0, leftover_len = 0;
2158 char star_match[STAR_MATCH_N][MSG_SIZ];
2159
2160 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2161    advance *index beyond it, and set leftover_start to the new value of
2162    *index; else return FALSE.  If pattern contains the character '*', it
2163    matches any sequence of characters not containing '\r', '\n', or the
2164    character following the '*' (if any), and the matched sequence(s) are
2165    copied into star_match.
2166    */
2167 int
2168 looking_at(buf, index, pattern)
2169      char *buf;
2170      int *index;
2171      char *pattern;
2172 {
2173     char *bufp = &buf[*index], *patternp = pattern;
2174     int star_count = 0;
2175     char *matchp = star_match[0];
2176
2177     for (;;) {
2178         if (*patternp == NULLCHAR) {
2179             *index = leftover_start = bufp - buf;
2180             *matchp = NULLCHAR;
2181             return TRUE;
2182         }
2183         if (*bufp == NULLCHAR) return FALSE;
2184         if (*patternp == '*') {
2185             if (*bufp == *(patternp + 1)) {
2186                 *matchp = NULLCHAR;
2187                 matchp = star_match[++star_count];
2188                 patternp += 2;
2189                 bufp++;
2190                 continue;
2191             } else if (*bufp == '\n' || *bufp == '\r') {
2192                 patternp++;
2193                 if (*patternp == NULLCHAR)
2194                   continue;
2195                 else
2196                   return FALSE;
2197             } else {
2198                 *matchp++ = *bufp++;
2199                 continue;
2200             }
2201         }
2202         if (*patternp != *bufp) return FALSE;
2203         patternp++;
2204         bufp++;
2205     }
2206 }
2207
2208 void
2209 SendToPlayer(data, length)
2210      char *data;
2211      int length;
2212 {
2213     int error, outCount;
2214     outCount = OutputToProcess(NoProc, data, length, &error);
2215     if (outCount < length) {
2216         DisplayFatalError(_("Error writing to display"), error, 1);
2217     }
2218 }
2219
2220 void
2221 PackHolding(packed, holding)
2222      char packed[];
2223      char *holding;
2224 {
2225     char *p = holding;
2226     char *q = packed;
2227     int runlength = 0;
2228     int curr = 9999;
2229     do {
2230         if (*p == curr) {
2231             runlength++;
2232         } else {
2233             switch (runlength) {
2234               case 0:
2235                 break;
2236               case 1:
2237                 *q++ = curr;
2238                 break;
2239               case 2:
2240                 *q++ = curr;
2241                 *q++ = curr;
2242                 break;
2243               default:
2244                 sprintf(q, "%d", runlength);
2245                 while (*q) q++;
2246                 *q++ = curr;
2247                 break;
2248             }
2249             runlength = 1;
2250             curr = *p;
2251         }
2252     } while (*p++);
2253     *q = NULLCHAR;
2254 }
2255
2256 /* Telnet protocol requests from the front end */
2257 void
2258 TelnetRequest(ddww, option)
2259      unsigned char ddww, option;
2260 {
2261     unsigned char msg[3];
2262     int outCount, outError;
2263
2264     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2265
2266     if (appData.debugMode) {
2267         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2268         switch (ddww) {
2269           case TN_DO:
2270             ddwwStr = "DO";
2271             break;
2272           case TN_DONT:
2273             ddwwStr = "DONT";
2274             break;
2275           case TN_WILL:
2276             ddwwStr = "WILL";
2277             break;
2278           case TN_WONT:
2279             ddwwStr = "WONT";
2280             break;
2281           default:
2282             ddwwStr = buf1;
2283             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2284             break;
2285         }
2286         switch (option) {
2287           case TN_ECHO:
2288             optionStr = "ECHO";
2289             break;
2290           default:
2291             optionStr = buf2;
2292             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2293             break;
2294         }
2295         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2296     }
2297     msg[0] = TN_IAC;
2298     msg[1] = ddww;
2299     msg[2] = option;
2300     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2301     if (outCount < 3) {
2302         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2303     }
2304 }
2305
2306 void
2307 DoEcho()
2308 {
2309     if (!appData.icsActive) return;
2310     TelnetRequest(TN_DO, TN_ECHO);
2311 }
2312
2313 void
2314 DontEcho()
2315 {
2316     if (!appData.icsActive) return;
2317     TelnetRequest(TN_DONT, TN_ECHO);
2318 }
2319
2320 void
2321 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2322 {
2323     /* put the holdings sent to us by the server on the board holdings area */
2324     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2325     char p;
2326     ChessSquare piece;
2327
2328     if(gameInfo.holdingsWidth < 2)  return;
2329     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2330         return; // prevent overwriting by pre-board holdings
2331
2332     if( (int)lowestPiece >= BlackPawn ) {
2333         holdingsColumn = 0;
2334         countsColumn = 1;
2335         holdingsStartRow = BOARD_HEIGHT-1;
2336         direction = -1;
2337     } else {
2338         holdingsColumn = BOARD_WIDTH-1;
2339         countsColumn = BOARD_WIDTH-2;
2340         holdingsStartRow = 0;
2341         direction = 1;
2342     }
2343
2344     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2345         board[i][holdingsColumn] = EmptySquare;
2346         board[i][countsColumn]   = (ChessSquare) 0;
2347     }
2348     while( (p=*holdings++) != NULLCHAR ) {
2349         piece = CharToPiece( ToUpper(p) );
2350         if(piece == EmptySquare) continue;
2351         /*j = (int) piece - (int) WhitePawn;*/
2352         j = PieceToNumber(piece);
2353         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2354         if(j < 0) continue;               /* should not happen */
2355         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2356         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2357         board[holdingsStartRow+j*direction][countsColumn]++;
2358     }
2359 }
2360
2361
2362 void
2363 VariantSwitch(Board board, VariantClass newVariant)
2364 {
2365    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2366    static Board oldBoard;
2367
2368    startedFromPositionFile = FALSE;
2369    if(gameInfo.variant == newVariant) return;
2370
2371    /* [HGM] This routine is called each time an assignment is made to
2372     * gameInfo.variant during a game, to make sure the board sizes
2373     * are set to match the new variant. If that means adding or deleting
2374     * holdings, we shift the playing board accordingly
2375     * This kludge is needed because in ICS observe mode, we get boards
2376     * of an ongoing game without knowing the variant, and learn about the
2377     * latter only later. This can be because of the move list we requested,
2378     * in which case the game history is refilled from the beginning anyway,
2379     * but also when receiving holdings of a crazyhouse game. In the latter
2380     * case we want to add those holdings to the already received position.
2381     */
2382
2383
2384    if (appData.debugMode) {
2385      fprintf(debugFP, "Switch board from %s to %s\n",
2386              VariantName(gameInfo.variant), VariantName(newVariant));
2387      setbuf(debugFP, NULL);
2388    }
2389    shuffleOpenings = 0;       /* [HGM] shuffle */
2390    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2391    switch(newVariant)
2392      {
2393      case VariantShogi:
2394        newWidth = 9;  newHeight = 9;
2395        gameInfo.holdingsSize = 7;
2396      case VariantBughouse:
2397      case VariantCrazyhouse:
2398        newHoldingsWidth = 2; break;
2399      case VariantGreat:
2400        newWidth = 10;
2401      case VariantSuper:
2402        newHoldingsWidth = 2;
2403        gameInfo.holdingsSize = 8;
2404        break;
2405      case VariantGothic:
2406      case VariantCapablanca:
2407      case VariantCapaRandom:
2408        newWidth = 10;
2409      default:
2410        newHoldingsWidth = gameInfo.holdingsSize = 0;
2411      };
2412
2413    if(newWidth  != gameInfo.boardWidth  ||
2414       newHeight != gameInfo.boardHeight ||
2415       newHoldingsWidth != gameInfo.holdingsWidth ) {
2416
2417      /* shift position to new playing area, if needed */
2418      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2419        for(i=0; i<BOARD_HEIGHT; i++)
2420          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2421            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2422              board[i][j];
2423        for(i=0; i<newHeight; i++) {
2424          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2425          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2426        }
2427      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432      }
2433      gameInfo.boardWidth  = newWidth;
2434      gameInfo.boardHeight = newHeight;
2435      gameInfo.holdingsWidth = newHoldingsWidth;
2436      gameInfo.variant = newVariant;
2437      InitDrawingSizes(-2, 0);
2438    } else gameInfo.variant = newVariant;
2439    CopyBoard(oldBoard, board);   // remember correctly formatted board
2440      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2441    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2442 }
2443
2444 static int loggedOn = FALSE;
2445
2446 /*-- Game start info cache: --*/
2447 int gs_gamenum;
2448 char gs_kind[MSG_SIZ];
2449 static char player1Name[128] = "";
2450 static char player2Name[128] = "";
2451 static char cont_seq[] = "\n\\   ";
2452 static int player1Rating = -1;
2453 static int player2Rating = -1;
2454 /*----------------------------*/
2455
2456 ColorClass curColor = ColorNormal;
2457 int suppressKibitz = 0;
2458
2459 // [HGM] seekgraph
2460 Boolean soughtPending = FALSE;
2461 Boolean seekGraphUp;
2462 #define MAX_SEEK_ADS 200
2463 #define SQUARE 0x80
2464 char *seekAdList[MAX_SEEK_ADS];
2465 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2466 float tcList[MAX_SEEK_ADS];
2467 char colorList[MAX_SEEK_ADS];
2468 int nrOfSeekAds = 0;
2469 int minRating = 1010, maxRating = 2800;
2470 int hMargin = 10, vMargin = 20, h, w;
2471 extern int squareSize, lineGap;
2472
2473 void
2474 PlotSeekAd(int i)
2475 {
2476         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2477         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2478         if(r < minRating+100 && r >=0 ) r = minRating+100;
2479         if(r > maxRating) r = maxRating;
2480         if(tc < 1.) tc = 1.;
2481         if(tc > 95.) tc = 95.;
2482         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2483         y = ((double)r - minRating)/(maxRating - minRating)
2484             * (h-vMargin-squareSize/8-1) + vMargin;
2485         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2486         if(strstr(seekAdList[i], " u ")) color = 1;
2487         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2488            !strstr(seekAdList[i], "bullet") &&
2489            !strstr(seekAdList[i], "blitz") &&
2490            !strstr(seekAdList[i], "standard") ) color = 2;
2491         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2492         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2493 }
2494
2495 void
2496 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2497 {
2498         char buf[MSG_SIZ], *ext = "";
2499         VariantClass v = StringToVariant(type);
2500         if(strstr(type, "wild")) {
2501             ext = type + 4; // append wild number
2502             if(v == VariantFischeRandom) type = "chess960"; else
2503             if(v == VariantLoadable) type = "setup"; else
2504             type = VariantName(v);
2505         }
2506         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2507         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2508             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2509             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2510             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2511             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2512             seekNrList[nrOfSeekAds] = nr;
2513             zList[nrOfSeekAds] = 0;
2514             seekAdList[nrOfSeekAds++] = StrSave(buf);
2515             if(plot) PlotSeekAd(nrOfSeekAds-1);
2516         }
2517 }
2518
2519 void
2520 EraseSeekDot(int i)
2521 {
2522     int x = xList[i], y = yList[i], d=squareSize/4, k;
2523     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2524     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2525     // now replot every dot that overlapped
2526     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2527         int xx = xList[k], yy = yList[k];
2528         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2529             DrawSeekDot(xx, yy, colorList[k]);
2530     }
2531 }
2532
2533 void
2534 RemoveSeekAd(int nr)
2535 {
2536         int i;
2537         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2538             EraseSeekDot(i);
2539             if(seekAdList[i]) free(seekAdList[i]);
2540             seekAdList[i] = seekAdList[--nrOfSeekAds];
2541             seekNrList[i] = seekNrList[nrOfSeekAds];
2542             ratingList[i] = ratingList[nrOfSeekAds];
2543             colorList[i]  = colorList[nrOfSeekAds];
2544             tcList[i] = tcList[nrOfSeekAds];
2545             xList[i]  = xList[nrOfSeekAds];
2546             yList[i]  = yList[nrOfSeekAds];
2547             zList[i]  = zList[nrOfSeekAds];
2548             seekAdList[nrOfSeekAds] = NULL;
2549             break;
2550         }
2551 }
2552
2553 Boolean
2554 MatchSoughtLine(char *line)
2555 {
2556     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2557     int nr, base, inc, u=0; char dummy;
2558
2559     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2561        (u=1) &&
2562        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2563         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2564         // match: compact and save the line
2565         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2566         return TRUE;
2567     }
2568     return FALSE;
2569 }
2570
2571 int
2572 DrawSeekGraph()
2573 {
2574     int i;
2575     if(!seekGraphUp) return FALSE;
2576     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2577     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2578
2579     DrawSeekBackground(0, 0, w, h);
2580     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2581     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2582     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2583         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2584         yy = h-1-yy;
2585         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2586         if(i%500 == 0) {
2587             char buf[MSG_SIZ];
2588             snprintf(buf, MSG_SIZ, "%d", i);
2589             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2590         }
2591     }
2592     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2593     for(i=1; i<100; i+=(i<10?1:5)) {
2594         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2595         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2596         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2600         }
2601     }
2602     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2603     return TRUE;
2604 }
2605
2606 int SeekGraphClick(ClickType click, int x, int y, int moving)
2607 {
2608     static int lastDown = 0, displayed = 0, lastSecond;
2609     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2610         if(click == Release || moving) return FALSE;
2611         nrOfSeekAds = 0;
2612         soughtPending = TRUE;
2613         SendToICS(ics_prefix);
2614         SendToICS("sought\n"); // should this be "sought all"?
2615     } else { // issue challenge based on clicked ad
2616         int dist = 10000; int i, closest = 0, second = 0;
2617         for(i=0; i<nrOfSeekAds; i++) {
2618             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2619             if(d < dist) { dist = d; closest = i; }
2620             second += (d - zList[i] < 120); // count in-range ads
2621             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2622         }
2623         if(dist < 120) {
2624             char buf[MSG_SIZ];
2625             second = (second > 1);
2626             if(displayed != closest || second != lastSecond) {
2627                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2628                 lastSecond = second; displayed = closest;
2629             }
2630             if(click == Press) {
2631                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2632                 lastDown = closest;
2633                 return TRUE;
2634             } // on press 'hit', only show info
2635             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2636             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2637             SendToICS(ics_prefix);
2638             SendToICS(buf);
2639             return TRUE; // let incoming board of started game pop down the graph
2640         } else if(click == Release) { // release 'miss' is ignored
2641             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2642             if(moving == 2) { // right up-click
2643                 nrOfSeekAds = 0; // refresh graph
2644                 soughtPending = TRUE;
2645                 SendToICS(ics_prefix);
2646                 SendToICS("sought\n"); // should this be "sought all"?
2647             }
2648             return TRUE;
2649         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2650         // press miss or release hit 'pop down' seek graph
2651         seekGraphUp = FALSE;
2652         DrawPosition(TRUE, NULL);
2653     }
2654     return TRUE;
2655 }
2656
2657 void
2658 read_from_ics(isr, closure, data, count, error)
2659      InputSourceRef isr;
2660      VOIDSTAR closure;
2661      char *data;
2662      int count;
2663      int error;
2664 {
2665 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2666 #define STARTED_NONE 0
2667 #define STARTED_MOVES 1
2668 #define STARTED_BOARD 2
2669 #define STARTED_OBSERVE 3
2670 #define STARTED_HOLDINGS 4
2671 #define STARTED_CHATTER 5
2672 #define STARTED_COMMENT 6
2673 #define STARTED_MOVES_NOHIDE 7
2674
2675     static int started = STARTED_NONE;
2676     static char parse[20000];
2677     static int parse_pos = 0;
2678     static char buf[BUF_SIZE + 1];
2679     static int firstTime = TRUE, intfSet = FALSE;
2680     static ColorClass prevColor = ColorNormal;
2681     static int savingComment = FALSE;
2682     static int cmatch = 0; // continuation sequence match
2683     char *bp;
2684     char str[MSG_SIZ];
2685     int i, oldi;
2686     int buf_len;
2687     int next_out;
2688     int tkind;
2689     int backup;    /* [DM] For zippy color lines */
2690     char *p;
2691     char talker[MSG_SIZ]; // [HGM] chat
2692     int channel;
2693
2694     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2695
2696     if (appData.debugMode) {
2697       if (!error) {
2698         fprintf(debugFP, "<ICS: ");
2699         show_bytes(debugFP, data, count);
2700         fprintf(debugFP, "\n");
2701       }
2702     }
2703
2704     if (appData.debugMode) { int f = forwardMostMove;
2705         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2706                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2707                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2708     }
2709     if (count > 0) {
2710         /* If last read ended with a partial line that we couldn't parse,
2711            prepend it to the new read and try again. */
2712         if (leftover_len > 0) {
2713             for (i=0; i<leftover_len; i++)
2714               buf[i] = buf[leftover_start + i];
2715         }
2716
2717     /* copy new characters into the buffer */
2718     bp = buf + leftover_len;
2719     buf_len=leftover_len;
2720     for (i=0; i<count; i++)
2721     {
2722         // ignore these
2723         if (data[i] == '\r')
2724             continue;
2725
2726         // join lines split by ICS?
2727         if (!appData.noJoin)
2728         {
2729             /*
2730                 Joining just consists of finding matches against the
2731                 continuation sequence, and discarding that sequence
2732                 if found instead of copying it.  So, until a match
2733                 fails, there's nothing to do since it might be the
2734                 complete sequence, and thus, something we don't want
2735                 copied.
2736             */
2737             if (data[i] == cont_seq[cmatch])
2738             {
2739                 cmatch++;
2740                 if (cmatch == strlen(cont_seq))
2741                 {
2742                     cmatch = 0; // complete match.  just reset the counter
2743
2744                     /*
2745                         it's possible for the ICS to not include the space
2746                         at the end of the last word, making our [correct]
2747                         join operation fuse two separate words.  the server
2748                         does this when the space occurs at the width setting.
2749                     */
2750                     if (!buf_len || buf[buf_len-1] != ' ')
2751                     {
2752                         *bp++ = ' ';
2753                         buf_len++;
2754                     }
2755                 }
2756                 continue;
2757             }
2758             else if (cmatch)
2759             {
2760                 /*
2761                     match failed, so we have to copy what matched before
2762                     falling through and copying this character.  In reality,
2763                     this will only ever be just the newline character, but
2764                     it doesn't hurt to be precise.
2765                 */
2766                 strncpy(bp, cont_seq, cmatch);
2767                 bp += cmatch;
2768                 buf_len += cmatch;
2769                 cmatch = 0;
2770             }
2771         }
2772
2773         // copy this char
2774         *bp++ = data[i];
2775         buf_len++;
2776     }
2777
2778         buf[buf_len] = NULLCHAR;
2779 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2780         next_out = 0;
2781         leftover_start = 0;
2782
2783         i = 0;
2784         while (i < buf_len) {
2785             /* Deal with part of the TELNET option negotiation
2786                protocol.  We refuse to do anything beyond the
2787                defaults, except that we allow the WILL ECHO option,
2788                which ICS uses to turn off password echoing when we are
2789                directly connected to it.  We reject this option
2790                if localLineEditing mode is on (always on in xboard)
2791                and we are talking to port 23, which might be a real
2792                telnet server that will try to keep WILL ECHO on permanently.
2793              */
2794             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2795                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2796                 unsigned char option;
2797                 oldi = i;
2798                 switch ((unsigned char) buf[++i]) {
2799                   case TN_WILL:
2800                     if (appData.debugMode)
2801                       fprintf(debugFP, "\n<WILL ");
2802                     switch (option = (unsigned char) buf[++i]) {
2803                       case TN_ECHO:
2804                         if (appData.debugMode)
2805                           fprintf(debugFP, "ECHO ");
2806                         /* Reply only if this is a change, according
2807                            to the protocol rules. */
2808                         if (remoteEchoOption) break;
2809                         if (appData.localLineEditing &&
2810                             atoi(appData.icsPort) == TN_PORT) {
2811                             TelnetRequest(TN_DONT, TN_ECHO);
2812                         } else {
2813                             EchoOff();
2814                             TelnetRequest(TN_DO, TN_ECHO);
2815                             remoteEchoOption = TRUE;
2816                         }
2817                         break;
2818                       default:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "%d ", option);
2821                         /* Whatever this is, we don't want it. */
2822                         TelnetRequest(TN_DONT, option);
2823                         break;
2824                     }
2825                     break;
2826                   case TN_WONT:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<WONT ");
2829                     switch (option = (unsigned char) buf[++i]) {
2830                       case TN_ECHO:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "ECHO ");
2833                         /* Reply only if this is a change, according
2834                            to the protocol rules. */
2835                         if (!remoteEchoOption) break;
2836                         EchoOn();
2837                         TelnetRequest(TN_DONT, TN_ECHO);
2838                         remoteEchoOption = FALSE;
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", (unsigned char) option);
2843                         /* Whatever this is, it must already be turned
2844                            off, because we never agree to turn on
2845                            anything non-default, so according to the
2846                            protocol rules, we don't reply. */
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DO:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DO ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         /* Whatever this is, we refuse to do it. */
2856                         if (appData.debugMode)
2857                           fprintf(debugFP, "%d ", option);
2858                         TelnetRequest(TN_WONT, option);
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DONT:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DONT ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         if (appData.debugMode)
2868                           fprintf(debugFP, "%d ", option);
2869                         /* Whatever this is, we are already not doing
2870                            it, because we never agree to do anything
2871                            non-default, so according to the protocol
2872                            rules, we don't reply. */
2873                         break;
2874                     }
2875                     break;
2876                   case TN_IAC:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<IAC ");
2879                     /* Doubled IAC; pass it through */
2880                     i--;
2881                     break;
2882                   default:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2885                     /* Drop all other telnet commands on the floor */
2886                     break;
2887                 }
2888                 if (oldi > next_out)
2889                   SendToPlayer(&buf[next_out], oldi - next_out);
2890                 if (++i > next_out)
2891                   next_out = i;
2892                 continue;
2893             }
2894
2895             /* OK, this at least will *usually* work */
2896             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2897                 loggedOn = TRUE;
2898             }
2899
2900             if (loggedOn && !intfSet) {
2901                 if (ics_type == ICS_ICC) {
2902                   snprintf(str, MSG_SIZ,
2903                           "/set-quietly interface %s\n/set-quietly style 12\n",
2904                           programVersion);
2905                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2906                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2907                 } else if (ics_type == ICS_CHESSNET) {
2908                   snprintf(str, MSG_SIZ, "/style 12\n");
2909                 } else {
2910                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2911                   strcat(str, programVersion);
2912                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2915 #ifdef WIN32
2916                   strcat(str, "$iset nohighlight 1\n");
2917 #endif
2918                   strcat(str, "$iset lock 1\n$style 12\n");
2919                 }
2920                 SendToICS(str);
2921                 NotifyFrontendLogin();
2922                 intfSet = TRUE;
2923             }
2924
2925             if (started == STARTED_COMMENT) {
2926                 /* Accumulate characters in comment */
2927                 parse[parse_pos++] = buf[i];
2928                 if (buf[i] == '\n') {
2929                     parse[parse_pos] = NULLCHAR;
2930                     if(chattingPartner>=0) {
2931                         char mess[MSG_SIZ];
2932                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2933                         OutputChatMessage(chattingPartner, mess);
2934                         chattingPartner = -1;
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     } else
2937                     if(!suppressKibitz) // [HGM] kibitz
2938                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2939                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2940                         int nrDigit = 0, nrAlph = 0, j;
2941                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2942                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2943                         parse[parse_pos] = NULLCHAR;
2944                         // try to be smart: if it does not look like search info, it should go to
2945                         // ICS interaction window after all, not to engine-output window.
2946                         for(j=0; j<parse_pos; j++) { // count letters and digits
2947                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2948                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2949                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2950                         }
2951                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2952                             int depth=0; float score;
2953                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2954                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2955                                 pvInfoList[forwardMostMove-1].depth = depth;
2956                                 pvInfoList[forwardMostMove-1].score = 100*score;
2957                             }
2958                             OutputKibitz(suppressKibitz, parse);
2959                         } else {
2960                             char tmp[MSG_SIZ];
2961                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2962                             SendToPlayer(tmp, strlen(tmp));
2963                         }
2964                         next_out = i+1; // [HGM] suppress printing in ICS window
2965                     }
2966                     started = STARTED_NONE;
2967                 } else {
2968                     /* Don't match patterns against characters in comment */
2969                     i++;
2970                     continue;
2971                 }
2972             }
2973             if (started == STARTED_CHATTER) {
2974                 if (buf[i] != '\n') {
2975                     /* Don't match patterns against characters in chatter */
2976                     i++;
2977                     continue;
2978                 }
2979                 started = STARTED_NONE;
2980                 if(suppressKibitz) next_out = i+1;
2981             }
2982
2983             /* Kludge to deal with rcmd protocol */
2984             if (firstTime && looking_at(buf, &i, "\001*")) {
2985                 DisplayFatalError(&buf[1], 0, 1);
2986                 continue;
2987             } else {
2988                 firstTime = FALSE;
2989             }
2990
2991             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2992                 ics_type = ICS_ICC;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2999                 ics_type = ICS_FICS;
3000                 ics_prefix = "$";
3001                 if (appData.debugMode)
3002                   fprintf(debugFP, "ics_type %d\n", ics_type);
3003                 continue;
3004             }
3005             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3006                 ics_type = ICS_CHESSNET;
3007                 ics_prefix = "/";
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, "ics_type %d\n", ics_type);
3010                 continue;
3011             }
3012
3013             if (!loggedOn &&
3014                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3015                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3016                  looking_at(buf, &i, "will be \"*\""))) {
3017               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3018               continue;
3019             }
3020
3021             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3022               char buf[MSG_SIZ];
3023               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3024               DisplayIcsInteractionTitle(buf);
3025               have_set_title = TRUE;
3026             }
3027
3028             /* skip finger notes */
3029             if (started == STARTED_NONE &&
3030                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3031                  (buf[i] == '1' && buf[i+1] == '0')) &&
3032                 buf[i+2] == ':' && buf[i+3] == ' ') {
3033               started = STARTED_CHATTER;
3034               i += 3;
3035               continue;
3036             }
3037
3038             oldi = i;
3039             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3040             if(appData.seekGraph) {
3041                 if(soughtPending && MatchSoughtLine(buf+i)) {
3042                     i = strstr(buf+i, "rated") - buf;
3043                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044                     next_out = leftover_start = i;
3045                     started = STARTED_CHATTER;
3046                     suppressKibitz = TRUE;
3047                     continue;
3048                 }
3049                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3050                         && looking_at(buf, &i, "* ads displayed")) {
3051                     soughtPending = FALSE;
3052                     seekGraphUp = TRUE;
3053                     DrawSeekGraph();
3054                     continue;
3055                 }
3056                 if(appData.autoRefresh) {
3057                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3058                         int s = (ics_type == ICS_ICC); // ICC format differs
3059                         if(seekGraphUp)
3060                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3061                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3064                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                         next_out = i; // suppress
3066                         continue;
3067                     }
3068                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3069                         char *p = star_match[0];
3070                         while(*p) {
3071                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3072                             while(*p && *p++ != ' '); // next
3073                         }
3074                         looking_at(buf, &i, "*% "); // eat prompt
3075                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                         next_out = i;
3077                         continue;
3078                     }
3079                 }
3080             }
3081
3082             /* skip formula vars */
3083             if (started == STARTED_NONE &&
3084                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3091             if (appData.autoKibitz && started == STARTED_NONE &&
3092                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3093                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3094                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3095                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3096                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3097                         suppressKibitz = TRUE;
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3101                                 && (gameMode == IcsPlayingWhite)) ||
3102                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3103                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3104                             started = STARTED_CHATTER; // own kibitz we simply discard
3105                         else {
3106                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3107                             parse_pos = 0; parse[0] = NULLCHAR;
3108                             savingComment = TRUE;
3109                             suppressKibitz = gameMode != IcsObserving ? 2 :
3110                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3111                         }
3112                         continue;
3113                 } else
3114                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3115                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3116                          && atoi(star_match[0])) {
3117                     // suppress the acknowledgements of our own autoKibitz
3118                     char *p;
3119                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3121                     SendToPlayer(star_match[0], strlen(star_match[0]));
3122                     if(looking_at(buf, &i, "*% ")) // eat prompt
3123                         suppressKibitz = FALSE;
3124                     next_out = i;
3125                     continue;
3126                 }
3127             } // [HGM] kibitz: end of patch
3128
3129             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3130
3131             // [HGM] chat: intercept tells by users for which we have an open chat window
3132             channel = -1;
3133             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3134                                            looking_at(buf, &i, "* whispers:") ||
3135                                            looking_at(buf, &i, "* kibitzes:") ||
3136                                            looking_at(buf, &i, "* shouts:") ||
3137                                            looking_at(buf, &i, "* c-shouts:") ||
3138                                            looking_at(buf, &i, "--> * ") ||
3139                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3140                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3141                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3142                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3143                 int p;
3144                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3145                 chattingPartner = -1;
3146
3147                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3148                 for(p=0; p<MAX_CHAT; p++) {
3149                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3150                     talker[0] = '['; strcat(talker, "] ");
3151                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3152                     chattingPartner = p; break;
3153                     }
3154                 } else
3155                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3156                 for(p=0; p<MAX_CHAT; p++) {
3157                     if(!strcmp("kibitzes", chatPartner[p])) {
3158                         talker[0] = '['; strcat(talker, "] ");
3159                         chattingPartner = p; break;
3160                     }
3161                 } else
3162                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3163                 for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("whispers", chatPartner[p])) {
3165                         talker[0] = '['; strcat(talker, "] ");
3166                         chattingPartner = p; break;
3167                     }
3168                 } else
3169                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3170                   if(buf[i-8] == '-' && buf[i-3] == 't')
3171                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3172                     if(!strcmp("c-shouts", chatPartner[p])) {
3173                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3174                         chattingPartner = p; break;
3175                     }
3176                   }
3177                   if(chattingPartner < 0)
3178                   for(p=0; p<MAX_CHAT; p++) {
3179                     if(!strcmp("shouts", chatPartner[p])) {
3180                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3181                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3182                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3183                         chattingPartner = p; break;
3184                     }
3185                   }
3186                 }
3187                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3188                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3189                     talker[0] = 0; Colorize(ColorTell, FALSE);
3190                     chattingPartner = p; break;
3191                 }
3192                 if(chattingPartner<0) i = oldi; else {
3193                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3194                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3195                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3196                     started = STARTED_COMMENT;
3197                     parse_pos = 0; parse[0] = NULLCHAR;
3198                     savingComment = 3 + chattingPartner; // counts as TRUE
3199                     suppressKibitz = TRUE;
3200                     continue;
3201                 }
3202             } // [HGM] chat: end of patch
3203
3204           backup = i;
3205             if (appData.zippyTalk || appData.zippyPlay) {
3206                 /* [DM] Backup address for color zippy lines */
3207 #if ZIPPY
3208                if (loggedOn == TRUE)
3209                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3210                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3211 #endif
3212             } // [DM] 'else { ' deleted
3213                 if (
3214                     /* Regular tells and says */
3215                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3216                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3217                     looking_at(buf, &i, "* says: ") ||
3218                     /* Don't color "message" or "messages" output */
3219                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3220                     looking_at(buf, &i, "*. * at *:*: ") ||
3221                     looking_at(buf, &i, "--* (*:*): ") ||
3222                     /* Message notifications (same color as tells) */
3223                     looking_at(buf, &i, "* has left a message ") ||
3224                     looking_at(buf, &i, "* just sent you a message:\n") ||
3225                     /* Whispers and kibitzes */
3226                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3227                     looking_at(buf, &i, "* kibitzes: ") ||
3228                     /* Channel tells */
3229                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3230
3231                   if (tkind == 1 && strchr(star_match[0], ':')) {
3232                       /* Avoid "tells you:" spoofs in channels */
3233                      tkind = 3;
3234                   }
3235                   if (star_match[0][0] == NULLCHAR ||
3236                       strchr(star_match[0], ' ') ||
3237                       (tkind == 3 && strchr(star_match[1], ' '))) {
3238                     /* Reject bogus matches */
3239                     i = oldi;
3240                   } else {
3241                     if (appData.colorize) {
3242                       if (oldi > next_out) {
3243                         SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = oldi;
3245                       }
3246                       switch (tkind) {
3247                       case 1:
3248                         Colorize(ColorTell, FALSE);
3249                         curColor = ColorTell;
3250                         break;
3251                       case 2:
3252                         Colorize(ColorKibitz, FALSE);
3253                         curColor = ColorKibitz;
3254                         break;
3255                       case 3:
3256                         p = strrchr(star_match[1], '(');
3257                         if (p == NULL) {
3258                           p = star_match[1];
3259                         } else {
3260                           p++;
3261                         }
3262                         if (atoi(p) == 1) {
3263                           Colorize(ColorChannel1, FALSE);
3264                           curColor = ColorChannel1;
3265                         } else {
3266                           Colorize(ColorChannel, FALSE);
3267                           curColor = ColorChannel;
3268                         }
3269                         break;
3270                       case 5:
3271                         curColor = ColorNormal;
3272                         break;
3273                       }
3274                     }
3275                     if (started == STARTED_NONE && appData.autoComment &&
3276                         (gameMode == IcsObserving ||
3277                          gameMode == IcsPlayingWhite ||
3278                          gameMode == IcsPlayingBlack)) {
3279                       parse_pos = i - oldi;
3280                       memcpy(parse, &buf[oldi], parse_pos);
3281                       parse[parse_pos] = NULLCHAR;
3282                       started = STARTED_COMMENT;
3283                       savingComment = TRUE;
3284                     } else {
3285                       started = STARTED_CHATTER;
3286                       savingComment = FALSE;
3287                     }
3288                     loggedOn = TRUE;
3289                     continue;
3290                   }
3291                 }
3292
3293                 if (looking_at(buf, &i, "* s-shouts: ") ||
3294                     looking_at(buf, &i, "* c-shouts: ")) {
3295                     if (appData.colorize) {
3296                         if (oldi > next_out) {
3297                             SendToPlayer(&buf[next_out], oldi - next_out);
3298                             next_out = oldi;
3299                         }
3300                         Colorize(ColorSShout, FALSE);
3301                         curColor = ColorSShout;
3302                     }
3303                     loggedOn = TRUE;
3304                     started = STARTED_CHATTER;
3305                     continue;
3306                 }
3307
3308                 if (looking_at(buf, &i, "--->")) {
3309                     loggedOn = TRUE;
3310                     continue;
3311                 }
3312
3313                 if (looking_at(buf, &i, "* shouts: ") ||
3314                     looking_at(buf, &i, "--> ")) {
3315                     if (appData.colorize) {
3316                         if (oldi > next_out) {
3317                             SendToPlayer(&buf[next_out], oldi - next_out);
3318                             next_out = oldi;
3319                         }
3320                         Colorize(ColorShout, FALSE);
3321                         curColor = ColorShout;
3322                     }
3323                     loggedOn = TRUE;
3324                     started = STARTED_CHATTER;
3325                     continue;
3326                 }
3327
3328                 if (looking_at( buf, &i, "Challenge:")) {
3329                     if (appData.colorize) {
3330                         if (oldi > next_out) {
3331                             SendToPlayer(&buf[next_out], oldi - next_out);
3332                             next_out = oldi;
3333                         }
3334                         Colorize(ColorChallenge, FALSE);
3335                         curColor = ColorChallenge;
3336                     }
3337                     loggedOn = TRUE;
3338                     continue;
3339                 }
3340
3341                 if (looking_at(buf, &i, "* offers you") ||
3342                     looking_at(buf, &i, "* offers to be") ||
3343                     looking_at(buf, &i, "* would like to") ||
3344                     looking_at(buf, &i, "* requests to") ||
3345                     looking_at(buf, &i, "Your opponent offers") ||
3346                     looking_at(buf, &i, "Your opponent requests")) {
3347
3348                     if (appData.colorize) {
3349                         if (oldi > next_out) {
3350                             SendToPlayer(&buf[next_out], oldi - next_out);
3351                             next_out = oldi;
3352                         }
3353                         Colorize(ColorRequest, FALSE);
3354                         curColor = ColorRequest;
3355                     }
3356                     continue;
3357                 }
3358
3359                 if (looking_at(buf, &i, "* (*) seeking")) {
3360                     if (appData.colorize) {
3361                         if (oldi > next_out) {
3362                             SendToPlayer(&buf[next_out], oldi - next_out);
3363                             next_out = oldi;
3364                         }
3365                         Colorize(ColorSeek, FALSE);
3366                         curColor = ColorSeek;
3367                     }
3368                     continue;
3369             }
3370
3371           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3372
3373             if (looking_at(buf, &i, "\\   ")) {
3374                 if (prevColor != ColorNormal) {
3375                     if (oldi > next_out) {
3376                         SendToPlayer(&buf[next_out], oldi - next_out);
3377                         next_out = oldi;
3378                     }
3379                     Colorize(prevColor, TRUE);
3380                     curColor = prevColor;
3381                 }
3382                 if (savingComment) {
3383                     parse_pos = i - oldi;
3384                     memcpy(parse, &buf[oldi], parse_pos);
3385                     parse[parse_pos] = NULLCHAR;
3386                     started = STARTED_COMMENT;
3387                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3388                         chattingPartner = savingComment - 3; // kludge to remember the box
3389                 } else {
3390                     started = STARTED_CHATTER;
3391                 }
3392                 continue;
3393             }
3394
3395             if (looking_at(buf, &i, "Black Strength :") ||
3396                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3397                 looking_at(buf, &i, "<10>") ||
3398                 looking_at(buf, &i, "#@#")) {
3399                 /* Wrong board style */
3400                 loggedOn = TRUE;
3401                 SendToICS(ics_prefix);
3402                 SendToICS("set style 12\n");
3403                 SendToICS(ics_prefix);
3404                 SendToICS("refresh\n");
3405                 continue;
3406             }
3407
3408             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3409                 ICSInitScript();
3410                 have_sent_ICS_logon = 1;
3411                 continue;
3412             }
3413
3414             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3415                 (looking_at(buf, &i, "\n<12> ") ||
3416                  looking_at(buf, &i, "<12> "))) {
3417                 loggedOn = TRUE;
3418                 if (oldi > next_out) {
3419                     SendToPlayer(&buf[next_out], oldi - next_out);
3420                 }
3421                 next_out = i;
3422                 started = STARTED_BOARD;
3423                 parse_pos = 0;
3424                 continue;
3425             }
3426
3427             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3428                 looking_at(buf, &i, "<b1> ")) {
3429                 if (oldi > next_out) {
3430                     SendToPlayer(&buf[next_out], oldi - next_out);
3431                 }
3432                 next_out = i;
3433                 started = STARTED_HOLDINGS;
3434                 parse_pos = 0;
3435                 continue;
3436             }
3437
3438             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3439                 loggedOn = TRUE;
3440                 /* Header for a move list -- first line */
3441
3442                 switch (ics_getting_history) {
3443                   case H_FALSE:
3444                     switch (gameMode) {
3445                       case IcsIdle:
3446                       case BeginningOfGame:
3447                         /* User typed "moves" or "oldmoves" while we
3448                            were idle.  Pretend we asked for these
3449                            moves and soak them up so user can step
3450                            through them and/or save them.
3451                            */
3452                         Reset(FALSE, TRUE);
3453                         gameMode = IcsObserving;
3454                         ModeHighlight();
3455                         ics_gamenum = -1;
3456                         ics_getting_history = H_GOT_UNREQ_HEADER;
3457                         break;
3458                       case EditGame: /*?*/
3459                       case EditPosition: /*?*/
3460                         /* Should above feature work in these modes too? */
3461                         /* For now it doesn't */
3462                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3463                         break;
3464                       default:
3465                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3466                         break;
3467                     }
3468                     break;
3469                   case H_REQUESTED:
3470                     /* Is this the right one? */
3471                     if (gameInfo.white && gameInfo.black &&
3472                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3473                         strcmp(gameInfo.black, star_match[2]) == 0) {
3474                         /* All is well */
3475                         ics_getting_history = H_GOT_REQ_HEADER;
3476                     }
3477                     break;
3478                   case H_GOT_REQ_HEADER:
3479                   case H_GOT_UNREQ_HEADER:
3480                   case H_GOT_UNWANTED_HEADER:
3481                   case H_GETTING_MOVES:
3482                     /* Should not happen */
3483                     DisplayError(_("Error gathering move list: two headers"), 0);
3484                     ics_getting_history = H_FALSE;
3485                     break;
3486                 }
3487
3488                 /* Save player ratings into gameInfo if needed */
3489                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3490                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3491                     (gameInfo.whiteRating == -1 ||
3492                      gameInfo.blackRating == -1)) {
3493
3494                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3495                     gameInfo.blackRating = string_to_rating(star_match[3]);
3496                     if (appData.debugMode)
3497                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3498                               gameInfo.whiteRating, gameInfo.blackRating);
3499                 }
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i,
3504               "* * match, initial time: * minute*, increment: * second")) {
3505                 /* Header for a move list -- second line */
3506                 /* Initial board will follow if this is a wild game */
3507                 if (gameInfo.event != NULL) free(gameInfo.event);
3508                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3509                 gameInfo.event = StrSave(str);
3510                 /* [HGM] we switched variant. Translate boards if needed. */
3511                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3512                 continue;
3513             }
3514
3515             if (looking_at(buf, &i, "Move  ")) {
3516                 /* Beginning of a move list */
3517                 switch (ics_getting_history) {
3518                   case H_FALSE:
3519                     /* Normally should not happen */
3520                     /* Maybe user hit reset while we were parsing */
3521                     break;
3522                   case H_REQUESTED:
3523                     /* Happens if we are ignoring a move list that is not
3524                      * the one we just requested.  Common if the user
3525                      * tries to observe two games without turning off
3526                      * getMoveList */
3527                     break;
3528                   case H_GETTING_MOVES:
3529                     /* Should not happen */
3530                     DisplayError(_("Error gathering move list: nested"), 0);
3531                     ics_getting_history = H_FALSE;
3532                     break;
3533                   case H_GOT_REQ_HEADER:
3534                     ics_getting_history = H_GETTING_MOVES;
3535                     started = STARTED_MOVES;
3536                     parse_pos = 0;
3537                     if (oldi > next_out) {
3538                         SendToPlayer(&buf[next_out], oldi - next_out);
3539                     }
3540                     break;
3541                   case H_GOT_UNREQ_HEADER:
3542                     ics_getting_history = H_GETTING_MOVES;
3543                     started = STARTED_MOVES_NOHIDE;
3544                     parse_pos = 0;
3545                     break;
3546                   case H_GOT_UNWANTED_HEADER:
3547                     ics_getting_history = H_FALSE;
3548                     break;
3549                 }
3550                 continue;
3551             }
3552
3553             if (looking_at(buf, &i, "% ") ||
3554                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3555                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3556                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3557                     soughtPending = FALSE;
3558                     seekGraphUp = TRUE;
3559                     DrawSeekGraph();
3560                 }
3561                 if(suppressKibitz) next_out = i;
3562                 savingComment = FALSE;
3563                 suppressKibitz = 0;
3564                 switch (started) {
3565                   case STARTED_MOVES:
3566                   case STARTED_MOVES_NOHIDE:
3567                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3568                     parse[parse_pos + i - oldi] = NULLCHAR;
3569                     ParseGameHistory(parse);
3570 #if ZIPPY
3571                     if (appData.zippyPlay && first.initDone) {
3572                         FeedMovesToProgram(&first, forwardMostMove);
3573                         if (gameMode == IcsPlayingWhite) {
3574                             if (WhiteOnMove(forwardMostMove)) {
3575                                 if (first.sendTime) {
3576                                   if (first.useColors) {
3577                                     SendToProgram("black\n", &first);
3578                                   }
3579                                   SendTimeRemaining(&first, TRUE);
3580                                 }
3581                                 if (first.useColors) {
3582                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3583                                 }
3584                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3585                                 first.maybeThinking = TRUE;
3586                             } else {
3587                                 if (first.usePlayother) {
3588                                   if (first.sendTime) {
3589                                     SendTimeRemaining(&first, TRUE);
3590                                   }
3591                                   SendToProgram("playother\n", &first);
3592                                   firstMove = FALSE;
3593                                 } else {
3594                                   firstMove = TRUE;
3595                                 }
3596                             }
3597                         } else if (gameMode == IcsPlayingBlack) {
3598                             if (!WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("white\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, FALSE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("black\n", &first);
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, FALSE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         }
3622                     }
3623 #endif
3624                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3625                         /* Moves came from oldmoves or moves command
3626                            while we weren't doing anything else.
3627                            */
3628                         currentMove = forwardMostMove;
3629                         ClearHighlights();/*!!could figure this out*/
3630                         flipView = appData.flipView;
3631                         DrawPosition(TRUE, boards[currentMove]);
3632                         DisplayBothClocks();
3633                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3634                                 gameInfo.white, gameInfo.black);
3635                         DisplayTitle(str);
3636                         gameMode = IcsIdle;
3637                     } else {
3638                         /* Moves were history of an active game */
3639                         if (gameInfo.resultDetails != NULL) {
3640                             free(gameInfo.resultDetails);
3641                             gameInfo.resultDetails = NULL;
3642                         }
3643                     }
3644                     HistorySet(parseList, backwardMostMove,
3645                                forwardMostMove, currentMove-1);
3646                     DisplayMove(currentMove - 1);
3647                     if (started == STARTED_MOVES) next_out = i;
3648                     started = STARTED_NONE;
3649                     ics_getting_history = H_FALSE;
3650                     break;
3651
3652                   case STARTED_OBSERVE:
3653                     started = STARTED_NONE;
3654                     SendToICS(ics_prefix);
3655                     SendToICS("refresh\n");
3656                     break;
3657
3658                   default:
3659                     break;
3660                 }
3661                 if(bookHit) { // [HGM] book: simulate book reply
3662                     static char bookMove[MSG_SIZ]; // a bit generous?
3663
3664                     programStats.nodes = programStats.depth = programStats.time =
3665                     programStats.score = programStats.got_only_move = 0;
3666                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3667
3668                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3669                     strcat(bookMove, bookHit);
3670                     HandleMachineMove(bookMove, &first);
3671                 }
3672                 continue;
3673             }
3674
3675             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3676                  started == STARTED_HOLDINGS ||
3677                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3678                 /* Accumulate characters in move list or board */
3679                 parse[parse_pos++] = buf[i];
3680             }
3681
3682             /* Start of game messages.  Mostly we detect start of game
3683                when the first board image arrives.  On some versions
3684                of the ICS, though, we need to do a "refresh" after starting
3685                to observe in order to get the current board right away. */
3686             if (looking_at(buf, &i, "Adding game * to observation list")) {
3687                 started = STARTED_OBSERVE;
3688                 continue;
3689             }
3690
3691             /* Handle auto-observe */
3692             if (appData.autoObserve &&
3693                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3694                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3695                 char *player;
3696                 /* Choose the player that was highlighted, if any. */
3697                 if (star_match[0][0] == '\033' ||
3698                     star_match[1][0] != '\033') {
3699                     player = star_match[0];
3700                 } else {
3701                     player = star_match[2];
3702                 }
3703                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3704                         ics_prefix, StripHighlightAndTitle(player));
3705                 SendToICS(str);
3706
3707                 /* Save ratings from notify string */
3708                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3709                 player1Rating = string_to_rating(star_match[1]);
3710                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3711                 player2Rating = string_to_rating(star_match[3]);
3712
3713                 if (appData.debugMode)
3714                   fprintf(debugFP,
3715                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3716                           player1Name, player1Rating,
3717                           player2Name, player2Rating);
3718
3719                 continue;
3720             }
3721
3722             /* Deal with automatic examine mode after a game,
3723                and with IcsObserving -> IcsExamining transition */
3724             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3725                 looking_at(buf, &i, "has made you an examiner of game *")) {
3726
3727                 int gamenum = atoi(star_match[0]);
3728                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3729                     gamenum == ics_gamenum) {
3730                     /* We were already playing or observing this game;
3731                        no need to refetch history */
3732                     gameMode = IcsExamining;
3733                     if (pausing) {
3734                         pauseExamForwardMostMove = forwardMostMove;
3735                     } else if (currentMove < forwardMostMove) {
3736                         ForwardInner(forwardMostMove);
3737                     }
3738                 } else {
3739                     /* I don't think this case really can happen */
3740                     SendToICS(ics_prefix);
3741                     SendToICS("refresh\n");
3742                 }
3743                 continue;
3744             }
3745
3746             /* Error messages */
3747 //          if (ics_user_moved) {
3748             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3749                 if (looking_at(buf, &i, "Illegal move") ||
3750                     looking_at(buf, &i, "Not a legal move") ||
3751                     looking_at(buf, &i, "Your king is in check") ||
3752                     looking_at(buf, &i, "It isn't your turn") ||
3753                     looking_at(buf, &i, "It is not your move")) {
3754                     /* Illegal move */
3755                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3756                         currentMove = forwardMostMove-1;
3757                         DisplayMove(currentMove - 1); /* before DMError */
3758                         DrawPosition(FALSE, boards[currentMove]);
3759                         SwitchClocks(forwardMostMove-1); // [HGM] race
3760                         DisplayBothClocks();
3761                     }
3762                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3763                     ics_user_moved = 0;
3764                     continue;
3765                 }
3766             }
3767
3768             if (looking_at(buf, &i, "still have time") ||
3769                 looking_at(buf, &i, "not out of time") ||
3770                 looking_at(buf, &i, "either player is out of time") ||
3771                 looking_at(buf, &i, "has timeseal; checking")) {
3772                 /* We must have called his flag a little too soon */
3773                 whiteFlag = blackFlag = FALSE;
3774                 continue;
3775             }
3776
3777             if (looking_at(buf, &i, "added * seconds to") ||
3778                 looking_at(buf, &i, "seconds were added to")) {
3779                 /* Update the clocks */
3780                 SendToICS(ics_prefix);
3781                 SendToICS("refresh\n");
3782                 continue;
3783             }
3784
3785             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3786                 ics_clock_paused = TRUE;
3787                 StopClocks();
3788                 continue;
3789             }
3790
3791             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3792                 ics_clock_paused = FALSE;
3793                 StartClocks();
3794                 continue;
3795             }
3796
3797             /* Grab player ratings from the Creating: message.
3798                Note we have to check for the special case when
3799                the ICS inserts things like [white] or [black]. */
3800             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3801                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3802                 /* star_matches:
3803                    0    player 1 name (not necessarily white)
3804                    1    player 1 rating
3805                    2    empty, white, or black (IGNORED)
3806                    3    player 2 name (not necessarily black)
3807                    4    player 2 rating
3808
3809                    The names/ratings are sorted out when the game
3810                    actually starts (below).
3811                 */
3812                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3813                 player1Rating = string_to_rating(star_match[1]);
3814                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3815                 player2Rating = string_to_rating(star_match[4]);
3816
3817                 if (appData.debugMode)
3818                   fprintf(debugFP,
3819                           "Ratings from 'Creating:' %s %d, %s %d\n",
3820                           player1Name, player1Rating,
3821                           player2Name, player2Rating);
3822
3823                 continue;
3824             }
3825
3826             /* Improved generic start/end-of-game messages */
3827             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3828                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3829                 /* If tkind == 0: */
3830                 /* star_match[0] is the game number */
3831                 /*           [1] is the white player's name */
3832                 /*           [2] is the black player's name */
3833                 /* For end-of-game: */
3834                 /*           [3] is the reason for the game end */
3835                 /*           [4] is a PGN end game-token, preceded by " " */
3836                 /* For start-of-game: */
3837                 /*           [3] begins with "Creating" or "Continuing" */
3838                 /*           [4] is " *" or empty (don't care). */
3839                 int gamenum = atoi(star_match[0]);
3840                 char *whitename, *blackname, *why, *endtoken;
3841                 ChessMove endtype = EndOfFile;
3842
3843                 if (tkind == 0) {
3844                   whitename = star_match[1];
3845                   blackname = star_match[2];
3846                   why = star_match[3];
3847                   endtoken = star_match[4];
3848                 } else {
3849                   whitename = star_match[1];
3850                   blackname = star_match[3];
3851                   why = star_match[5];
3852                   endtoken = star_match[6];
3853                 }
3854
3855                 /* Game start messages */
3856                 if (strncmp(why, "Creating ", 9) == 0 ||
3857                     strncmp(why, "Continuing ", 11) == 0) {
3858                     gs_gamenum = gamenum;
3859                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3860                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3861                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3862 #if ZIPPY
3863                     if (appData.zippyPlay) {
3864                         ZippyGameStart(whitename, blackname);
3865                     }
3866 #endif /*ZIPPY*/
3867                     partnerBoardValid = FALSE; // [HGM] bughouse
3868                     continue;
3869                 }
3870
3871                 /* Game end messages */
3872                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3873                     ics_gamenum != gamenum) {
3874                     continue;
3875                 }
3876                 while (endtoken[0] == ' ') endtoken++;
3877                 switch (endtoken[0]) {
3878                   case '*':
3879                   default:
3880                     endtype = GameUnfinished;
3881                     break;
3882                   case '0':
3883                     endtype = BlackWins;
3884                     break;
3885                   case '1':
3886                     if (endtoken[1] == '/')
3887                       endtype = GameIsDrawn;
3888                     else
3889                       endtype = WhiteWins;
3890                     break;
3891                 }
3892                 GameEnds(endtype, why, GE_ICS);
3893 #if ZIPPY
3894                 if (appData.zippyPlay && first.initDone) {
3895                     ZippyGameEnd(endtype, why);
3896                     if (first.pr == NoProc) {
3897                       /* Start the next process early so that we'll
3898                          be ready for the next challenge */
3899                       StartChessProgram(&first);
3900                     }
3901                     /* Send "new" early, in case this command takes
3902                        a long time to finish, so that we'll be ready
3903                        for the next challenge. */
3904                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3905                     Reset(TRUE, TRUE);
3906                 }
3907 #endif /*ZIPPY*/
3908                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3909                 continue;
3910             }
3911
3912             if (looking_at(buf, &i, "Removing game * from observation") ||
3913                 looking_at(buf, &i, "no longer observing game *") ||
3914                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3915                 if (gameMode == IcsObserving &&
3916                     atoi(star_match[0]) == ics_gamenum)
3917                   {
3918                       /* icsEngineAnalyze */
3919                       if (appData.icsEngineAnalyze) {
3920                             ExitAnalyzeMode();
3921                             ModeHighlight();
3922                       }
3923                       StopClocks();
3924                       gameMode = IcsIdle;
3925                       ics_gamenum = -1;
3926                       ics_user_moved = FALSE;
3927                   }
3928                 continue;
3929             }
3930
3931             if (looking_at(buf, &i, "no longer examining game *")) {
3932                 if (gameMode == IcsExamining &&
3933                     atoi(star_match[0]) == ics_gamenum)
3934                   {
3935                       gameMode = IcsIdle;
3936                       ics_gamenum = -1;
3937                       ics_user_moved = FALSE;
3938                   }
3939                 continue;
3940             }
3941
3942             /* Advance leftover_start past any newlines we find,
3943                so only partial lines can get reparsed */
3944             if (looking_at(buf, &i, "\n")) {
3945                 prevColor = curColor;
3946                 if (curColor != ColorNormal) {
3947                     if (oldi > next_out) {
3948                         SendToPlayer(&buf[next_out], oldi - next_out);
3949                         next_out = oldi;
3950                     }
3951                     Colorize(ColorNormal, FALSE);
3952                     curColor = ColorNormal;
3953                 }
3954                 if (started == STARTED_BOARD) {
3955                     started = STARTED_NONE;
3956                     parse[parse_pos] = NULLCHAR;
3957                     ParseBoard12(parse);
3958                     ics_user_moved = 0;
3959
3960                     /* Send premove here */
3961                     if (appData.premove) {
3962                       char str[MSG_SIZ];
3963                       if (currentMove == 0 &&
3964                           gameMode == IcsPlayingWhite &&
3965                           appData.premoveWhite) {
3966                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3967                         if (appData.debugMode)
3968                           fprintf(debugFP, "Sending premove:\n");
3969                         SendToICS(str);
3970                       } else if (currentMove == 1 &&
3971                                  gameMode == IcsPlayingBlack &&
3972                                  appData.premoveBlack) {
3973                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                         SendToICS(str);
3977                       } else if (gotPremove) {
3978                         gotPremove = 0;
3979                         ClearPremoveHighlights();
3980                         if (appData.debugMode)
3981                           fprintf(debugFP, "Sending premove:\n");
3982                           UserMoveEvent(premoveFromX, premoveFromY,
3983                                         premoveToX, premoveToY,
3984                                         premovePromoChar);
3985                       }
3986                     }
3987
3988                     /* Usually suppress following prompt */
3989                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3990                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3991                         if (looking_at(buf, &i, "*% ")) {
3992                             savingComment = FALSE;
3993                             suppressKibitz = 0;
3994                         }
3995                     }
3996                     next_out = i;
3997                 } else if (started == STARTED_HOLDINGS) {
3998                     int gamenum;
3999                     char new_piece[MSG_SIZ];
4000                     started = STARTED_NONE;
4001                     parse[parse_pos] = NULLCHAR;
4002                     if (appData.debugMode)
4003                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4004                                                         parse, currentMove);
4005                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4006                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4007                         if (gameInfo.variant == VariantNormal) {
4008                           /* [HGM] We seem to switch variant during a game!
4009                            * Presumably no holdings were displayed, so we have
4010                            * to move the position two files to the right to
4011                            * create room for them!
4012                            */
4013                           VariantClass newVariant;
4014                           switch(gameInfo.boardWidth) { // base guess on board width
4015                                 case 9:  newVariant = VariantShogi; break;
4016                                 case 10: newVariant = VariantGreat; break;
4017                                 default: newVariant = VariantCrazyhouse; break;
4018                           }
4019                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4020                           /* Get a move list just to see the header, which
4021                              will tell us whether this is really bug or zh */
4022                           if (ics_getting_history == H_FALSE) {
4023                             ics_getting_history = H_REQUESTED;
4024                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4025                             SendToICS(str);
4026                           }
4027                         }
4028                         new_piece[0] = NULLCHAR;
4029                         sscanf(parse, "game %d white [%s black [%s <- %s",
4030                                &gamenum, white_holding, black_holding,
4031                                new_piece);
4032                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4033                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4034                         /* [HGM] copy holdings to board holdings area */
4035                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4036                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4037                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4038 #if ZIPPY
4039                         if (appData.zippyPlay && first.initDone) {
4040                             ZippyHoldings(white_holding, black_holding,
4041                                           new_piece);
4042                         }
4043 #endif /*ZIPPY*/
4044                         if (tinyLayout || smallLayout) {
4045                             char wh[16], bh[16];
4046                             PackHolding(wh, white_holding);
4047                             PackHolding(bh, black_holding);
4048                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4049                                     gameInfo.white, gameInfo.black);
4050                         } else {
4051                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4052                                     gameInfo.white, white_holding,
4053                                     gameInfo.black, black_holding);
4054                         }
4055                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4056                         DrawPosition(FALSE, boards[currentMove]);
4057                         DisplayTitle(str);
4058                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4059                         sscanf(parse, "game %d white [%s black [%s <- %s",
4060                                &gamenum, white_holding, black_holding,
4061                                new_piece);
4062                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4063                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4064                         /* [HGM] copy holdings to partner-board holdings area */
4065                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4066                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4067                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4068                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4069                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4070                       }
4071                     }
4072                     /* Suppress following prompt */
4073                     if (looking_at(buf, &i, "*% ")) {
4074                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4075                         savingComment = FALSE;
4076                         suppressKibitz = 0;
4077                     }
4078                     next_out = i;
4079                 }
4080                 continue;
4081             }
4082
4083             i++;                /* skip unparsed character and loop back */
4084         }
4085
4086         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4087 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4088 //          SendToPlayer(&buf[next_out], i - next_out);
4089             started != STARTED_HOLDINGS && leftover_start > next_out) {
4090             SendToPlayer(&buf[next_out], leftover_start - next_out);
4091             next_out = i;
4092         }
4093
4094         leftover_len = buf_len - leftover_start;
4095         /* if buffer ends with something we couldn't parse,
4096            reparse it after appending the next read */
4097
4098     } else if (count == 0) {
4099         RemoveInputSource(isr);
4100         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4101     } else {
4102         DisplayFatalError(_("Error reading from ICS"), error, 1);
4103     }
4104 }
4105
4106
4107 /* Board style 12 looks like this:
4108
4109    <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
4110
4111  * The "<12> " is stripped before it gets to this routine.  The two
4112  * trailing 0's (flip state and clock ticking) are later addition, and
4113  * some chess servers may not have them, or may have only the first.
4114  * Additional trailing fields may be added in the future.
4115  */
4116
4117 #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"
4118
4119 #define RELATION_OBSERVING_PLAYED    0
4120 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4121 #define RELATION_PLAYING_MYMOVE      1
4122 #define RELATION_PLAYING_NOTMYMOVE  -1
4123 #define RELATION_EXAMINING           2
4124 #define RELATION_ISOLATED_BOARD     -3
4125 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4126
4127 void
4128 ParseBoard12(string)
4129      char *string;
4130 {
4131     GameMode newGameMode;
4132     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4133     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4134     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4135     char to_play, board_chars[200];
4136     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4137     char black[32], white[32];
4138     Board board;
4139     int prevMove = currentMove;
4140     int ticking = 2;
4141     ChessMove moveType;
4142     int fromX, fromY, toX, toY;
4143     char promoChar;
4144     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4145     char *bookHit = NULL; // [HGM] book
4146     Boolean weird = FALSE, reqFlag = FALSE;
4147
4148     fromX = fromY = toX = toY = -1;
4149
4150     newGame = FALSE;
4151
4152     if (appData.debugMode)
4153       fprintf(debugFP, _("Parsing board: %s\n"), string);
4154
4155     move_str[0] = NULLCHAR;
4156     elapsed_time[0] = NULLCHAR;
4157     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4158         int  i = 0, j;
4159         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4160             if(string[i] == ' ') { ranks++; files = 0; }
4161             else files++;
4162             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4163             i++;
4164         }
4165         for(j = 0; j <i; j++) board_chars[j] = string[j];
4166         board_chars[i] = '\0';
4167         string += i + 1;
4168     }
4169     n = sscanf(string, PATTERN, &to_play, &double_push,
4170                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4171                &gamenum, white, black, &relation, &basetime, &increment,
4172                &white_stren, &black_stren, &white_time, &black_time,
4173                &moveNum, str, elapsed_time, move_str, &ics_flip,
4174                &ticking);
4175
4176     if (n < 21) {
4177         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4178         DisplayError(str, 0);
4179         return;
4180     }
4181
4182     /* Convert the move number to internal form */
4183     moveNum = (moveNum - 1) * 2;
4184     if (to_play == 'B') moveNum++;
4185     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4186       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4187                         0, 1);
4188       return;
4189     }
4190
4191     switch (relation) {
4192       case RELATION_OBSERVING_PLAYED:
4193       case RELATION_OBSERVING_STATIC:
4194         if (gamenum == -1) {
4195             /* Old ICC buglet */
4196             relation = RELATION_OBSERVING_STATIC;
4197         }
4198         newGameMode = IcsObserving;
4199         break;
4200       case RELATION_PLAYING_MYMOVE:
4201       case RELATION_PLAYING_NOTMYMOVE:
4202         newGameMode =
4203           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4204             IcsPlayingWhite : IcsPlayingBlack;
4205         break;
4206       case RELATION_EXAMINING:
4207         newGameMode = IcsExamining;
4208         break;
4209       case RELATION_ISOLATED_BOARD:
4210       default:
4211         /* Just display this board.  If user was doing something else,
4212            we will forget about it until the next board comes. */
4213         newGameMode = IcsIdle;
4214         break;
4215       case RELATION_STARTING_POSITION:
4216         newGameMode = gameMode;
4217         break;
4218     }
4219
4220     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4221          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4222       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4223       char *toSqr;
4224       for (k = 0; k < ranks; k++) {
4225         for (j = 0; j < files; j++)
4226           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4227         if(gameInfo.holdingsWidth > 1) {
4228              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4229              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4230         }
4231       }
4232       CopyBoard(partnerBoard, board);
4233       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4234         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4235         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4236       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4237       if(toSqr = strchr(str, '-')) {
4238         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4239         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4240       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4241       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4242       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4243       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4244       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4245       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4246                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4247       DisplayMessage(partnerStatus, "");
4248         partnerBoardValid = TRUE;
4249       return;
4250     }
4251
4252     /* Modify behavior for initial board display on move listing
4253        of wild games.
4254        */
4255     switch (ics_getting_history) {
4256       case H_FALSE:
4257       case H_REQUESTED:
4258         break;
4259       case H_GOT_REQ_HEADER:
4260       case H_GOT_UNREQ_HEADER:
4261         /* This is the initial position of the current game */
4262         gamenum = ics_gamenum;
4263         moveNum = 0;            /* old ICS bug workaround */
4264         if (to_play == 'B') {
4265           startedFromSetupPosition = TRUE;
4266           blackPlaysFirst = TRUE;
4267           moveNum = 1;
4268           if (forwardMostMove == 0) forwardMostMove = 1;
4269           if (backwardMostMove == 0) backwardMostMove = 1;
4270           if (currentMove == 0) currentMove = 1;
4271         }
4272         newGameMode = gameMode;
4273         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4274         break;
4275       case H_GOT_UNWANTED_HEADER:
4276         /* This is an initial board that we don't want */
4277         return;
4278       case H_GETTING_MOVES:
4279         /* Should not happen */
4280         DisplayError(_("Error gathering move list: extra board"), 0);
4281         ics_getting_history = H_FALSE;
4282         return;
4283     }
4284
4285    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4286                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4287      /* [HGM] We seem to have switched variant unexpectedly
4288       * Try to guess new variant from board size
4289       */
4290           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4291           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4292           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4293           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4294           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4295           if(!weird) newVariant = VariantNormal;
4296           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4297           /* Get a move list just to see the header, which
4298              will tell us whether this is really bug or zh */
4299           if (ics_getting_history == H_FALSE) {
4300             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4301             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4302             SendToICS(str);
4303           }
4304     }
4305
4306     /* Take action if this is the first board of a new game, or of a
4307        different game than is currently being displayed.  */
4308     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4309         relation == RELATION_ISOLATED_BOARD) {
4310
4311         /* Forget the old game and get the history (if any) of the new one */
4312         if (gameMode != BeginningOfGame) {
4313           Reset(TRUE, TRUE);
4314         }
4315         newGame = TRUE;
4316         if (appData.autoRaiseBoard) BoardToTop();
4317         prevMove = -3;
4318         if (gamenum == -1) {
4319             newGameMode = IcsIdle;
4320         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4321                    appData.getMoveList && !reqFlag) {
4322             /* Need to get game history */
4323             ics_getting_history = H_REQUESTED;
4324             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4325             SendToICS(str);
4326         }
4327
4328         /* Initially flip the board to have black on the bottom if playing
4329            black or if the ICS flip flag is set, but let the user change
4330            it with the Flip View button. */
4331         flipView = appData.autoFlipView ?
4332           (newGameMode == IcsPlayingBlack) || ics_flip :
4333           appData.flipView;
4334
4335         /* Done with values from previous mode; copy in new ones */
4336         gameMode = newGameMode;
4337         ModeHighlight();
4338         ics_gamenum = gamenum;
4339         if (gamenum == gs_gamenum) {
4340             int klen = strlen(gs_kind);
4341             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4342             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4343             gameInfo.event = StrSave(str);
4344         } else {
4345             gameInfo.event = StrSave("ICS game");
4346         }
4347         gameInfo.site = StrSave(appData.icsHost);
4348         gameInfo.date = PGNDate();
4349         gameInfo.round = StrSave("-");
4350         gameInfo.white = StrSave(white);
4351         gameInfo.black = StrSave(black);
4352         timeControl = basetime * 60 * 1000;
4353         timeControl_2 = 0;
4354         timeIncrement = increment * 1000;
4355         movesPerSession = 0;
4356         gameInfo.timeControl = TimeControlTagValue();
4357         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4358   if (appData.debugMode) {
4359     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4360     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4361     setbuf(debugFP, NULL);
4362   }
4363
4364         gameInfo.outOfBook = NULL;
4365
4366         /* Do we have the ratings? */
4367         if (strcmp(player1Name, white) == 0 &&
4368             strcmp(player2Name, black) == 0) {
4369             if (appData.debugMode)
4370               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4371                       player1Rating, player2Rating);
4372             gameInfo.whiteRating = player1Rating;
4373             gameInfo.blackRating = player2Rating;
4374         } else if (strcmp(player2Name, white) == 0 &&
4375                    strcmp(player1Name, black) == 0) {
4376             if (appData.debugMode)
4377               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4378                       player2Rating, player1Rating);
4379             gameInfo.whiteRating = player2Rating;
4380             gameInfo.blackRating = player1Rating;
4381         }
4382         player1Name[0] = player2Name[0] = NULLCHAR;
4383
4384         /* Silence shouts if requested */
4385         if (appData.quietPlay &&
4386             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4387             SendToICS(ics_prefix);
4388             SendToICS("set shout 0\n");
4389         }
4390     }
4391
4392     /* Deal with midgame name changes */
4393     if (!newGame) {
4394         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4395             if (gameInfo.white) free(gameInfo.white);
4396             gameInfo.white = StrSave(white);
4397         }
4398         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4399             if (gameInfo.black) free(gameInfo.black);
4400             gameInfo.black = StrSave(black);
4401         }
4402     }
4403
4404     /* Throw away game result if anything actually changes in examine mode */
4405     if (gameMode == IcsExamining && !newGame) {
4406         gameInfo.result = GameUnfinished;
4407         if (gameInfo.resultDetails != NULL) {
4408             free(gameInfo.resultDetails);
4409             gameInfo.resultDetails = NULL;
4410         }
4411     }
4412
4413     /* In pausing && IcsExamining mode, we ignore boards coming
4414        in if they are in a different variation than we are. */
4415     if (pauseExamInvalid) return;
4416     if (pausing && gameMode == IcsExamining) {
4417         if (moveNum <= pauseExamForwardMostMove) {
4418             pauseExamInvalid = TRUE;
4419             forwardMostMove = pauseExamForwardMostMove;
4420             return;
4421         }
4422     }
4423
4424   if (appData.debugMode) {
4425     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4426   }
4427     /* Parse the board */
4428     for (k = 0; k < ranks; k++) {
4429       for (j = 0; j < files; j++)
4430         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4431       if(gameInfo.holdingsWidth > 1) {
4432            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4433            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4434       }
4435     }
4436     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4437       board[5][BOARD_RGHT+1] = WhiteAngel;
4438       board[6][BOARD_RGHT+1] = WhiteMarshall;
4439       board[1][0] = BlackMarshall;
4440       board[2][0] = BlackAngel;
4441       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4442     }
4443     CopyBoard(boards[moveNum], board);
4444     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4445     if (moveNum == 0) {
4446         startedFromSetupPosition =
4447           !CompareBoards(board, initialPosition);
4448         if(startedFromSetupPosition)
4449             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4450     }
4451
4452     /* [HGM] Set castling rights. Take the outermost Rooks,
4453        to make it also work for FRC opening positions. Note that board12
4454        is really defective for later FRC positions, as it has no way to
4455        indicate which Rook can castle if they are on the same side of King.
4456        For the initial position we grant rights to the outermost Rooks,
4457        and remember thos rights, and we then copy them on positions
4458        later in an FRC game. This means WB might not recognize castlings with
4459        Rooks that have moved back to their original position as illegal,
4460        but in ICS mode that is not its job anyway.
4461     */
4462     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4463     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4464
4465         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4466             if(board[0][i] == WhiteRook) j = i;
4467         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4468         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4469             if(board[0][i] == WhiteRook) j = i;
4470         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4471         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4472             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4473         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4474         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4475             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4476         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4477
4478         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4479         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4480         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4481             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4482         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4483             if(board[BOARD_HEIGHT-1][k] == bKing)
4484                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4485         if(gameInfo.variant == VariantTwoKings) {
4486             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4487             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4488             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4489         }
4490     } else { int r;
4491         r = boards[moveNum][CASTLING][0] = initialRights[0];
4492         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4493         r = boards[moveNum][CASTLING][1] = initialRights[1];
4494         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4495         r = boards[moveNum][CASTLING][3] = initialRights[3];
4496         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4497         r = boards[moveNum][CASTLING][4] = initialRights[4];
4498         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4499         /* wildcastle kludge: always assume King has rights */
4500         r = boards[moveNum][CASTLING][2] = initialRights[2];
4501         r = boards[moveNum][CASTLING][5] = initialRights[5];
4502     }
4503     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4504     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4505
4506
4507     if (ics_getting_history == H_GOT_REQ_HEADER ||
4508         ics_getting_history == H_GOT_UNREQ_HEADER) {
4509         /* This was an initial position from a move list, not
4510            the current position */
4511         return;
4512     }
4513
4514     /* Update currentMove and known move number limits */
4515     newMove = newGame || moveNum > forwardMostMove;
4516
4517     if (newGame) {
4518         forwardMostMove = backwardMostMove = currentMove = moveNum;
4519         if (gameMode == IcsExamining && moveNum == 0) {
4520           /* Workaround for ICS limitation: we are not told the wild
4521              type when starting to examine a game.  But if we ask for
4522              the move list, the move list header will tell us */
4523             ics_getting_history = H_REQUESTED;
4524             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4525             SendToICS(str);
4526         }
4527     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4528                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4529 #if ZIPPY
4530         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4531         /* [HGM] applied this also to an engine that is silently watching        */
4532         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4533             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4534             gameInfo.variant == currentlyInitializedVariant) {
4535           takeback = forwardMostMove - moveNum;
4536           for (i = 0; i < takeback; i++) {
4537             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4538             SendToProgram("undo\n", &first);
4539           }
4540         }
4541 #endif
4542
4543         forwardMostMove = moveNum;
4544         if (!pausing || currentMove > forwardMostMove)
4545           currentMove = forwardMostMove;
4546     } else {
4547         /* New part of history that is not contiguous with old part */
4548         if (pausing && gameMode == IcsExamining) {
4549             pauseExamInvalid = TRUE;
4550             forwardMostMove = pauseExamForwardMostMove;
4551             return;
4552         }
4553         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4554 #if ZIPPY
4555             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4556                 // [HGM] when we will receive the move list we now request, it will be
4557                 // fed to the engine from the first move on. So if the engine is not
4558                 // in the initial position now, bring it there.
4559                 InitChessProgram(&first, 0);
4560             }
4561 #endif
4562             ics_getting_history = H_REQUESTED;
4563             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4564             SendToICS(str);
4565         }
4566         forwardMostMove = backwardMostMove = currentMove = moveNum;
4567     }
4568
4569     /* Update the clocks */
4570     if (strchr(elapsed_time, '.')) {
4571       /* Time is in ms */
4572       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4573       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4574     } else {
4575       /* Time is in seconds */
4576       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4577       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4578     }
4579
4580
4581 #if ZIPPY
4582     if (appData.zippyPlay && newGame &&
4583         gameMode != IcsObserving && gameMode != IcsIdle &&
4584         gameMode != IcsExamining)
4585       ZippyFirstBoard(moveNum, basetime, increment);
4586 #endif
4587
4588     /* Put the move on the move list, first converting
4589        to canonical algebraic form. */
4590     if (moveNum > 0) {
4591   if (appData.debugMode) {
4592     if (appData.debugMode) { int f = forwardMostMove;
4593         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4594                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4595                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4596     }
4597     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4598     fprintf(debugFP, "moveNum = %d\n", moveNum);
4599     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4600     setbuf(debugFP, NULL);
4601   }
4602         if (moveNum <= backwardMostMove) {
4603             /* We don't know what the board looked like before
4604                this move.  Punt. */
4605           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4606             strcat(parseList[moveNum - 1], " ");
4607             strcat(parseList[moveNum - 1], elapsed_time);
4608             moveList[moveNum - 1][0] = NULLCHAR;
4609         } else if (strcmp(move_str, "none") == 0) {
4610             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4611             /* Again, we don't know what the board looked like;
4612                this is really the start of the game. */
4613             parseList[moveNum - 1][0] = NULLCHAR;
4614             moveList[moveNum - 1][0] = NULLCHAR;
4615             backwardMostMove = moveNum;
4616             startedFromSetupPosition = TRUE;
4617             fromX = fromY = toX = toY = -1;
4618         } else {
4619           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4620           //                 So we parse the long-algebraic move string in stead of the SAN move
4621           int valid; char buf[MSG_SIZ], *prom;
4622
4623           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4624                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4625           // str looks something like "Q/a1-a2"; kill the slash
4626           if(str[1] == '/')
4627             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4628           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4629           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4630                 strcat(buf, prom); // long move lacks promo specification!
4631           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4632                 if(appData.debugMode)
4633                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4634                 safeStrCpy(move_str, buf, MSG_SIZ);
4635           }
4636           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4637                                 &fromX, &fromY, &toX, &toY, &promoChar)
4638                || ParseOneMove(buf, moveNum - 1, &moveType,
4639                                 &fromX, &fromY, &toX, &toY, &promoChar);
4640           // end of long SAN patch
4641           if (valid) {
4642             (void) CoordsToAlgebraic(boards[moveNum - 1],
4643                                      PosFlags(moveNum - 1),
4644                                      fromY, fromX, toY, toX, promoChar,
4645                                      parseList[moveNum-1]);
4646             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4647               case MT_NONE:
4648               case MT_STALEMATE:
4649               default:
4650                 break;
4651               case MT_CHECK:
4652                 if(gameInfo.variant != VariantShogi)
4653                     strcat(parseList[moveNum - 1], "+");
4654                 break;
4655               case MT_CHECKMATE:
4656               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4657                 strcat(parseList[moveNum - 1], "#");
4658                 break;
4659             }
4660             strcat(parseList[moveNum - 1], " ");
4661             strcat(parseList[moveNum - 1], elapsed_time);
4662             /* currentMoveString is set as a side-effect of ParseOneMove */
4663             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4664             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4665             strcat(moveList[moveNum - 1], "\n");
4666
4667             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4668                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4669               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4670                 ChessSquare old, new = boards[moveNum][k][j];
4671                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4672                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4673                   if(old == new) continue;
4674                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4675                   else if(new == WhiteWazir || new == BlackWazir) {
4676                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4677                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4678                       else boards[moveNum][k][j] = old; // preserve type of Gold
4679                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4680                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4681               }
4682           } else {
4683             /* Move from ICS was illegal!?  Punt. */
4684             if (appData.debugMode) {
4685               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4686               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4687             }
4688             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4689             strcat(parseList[moveNum - 1], " ");
4690             strcat(parseList[moveNum - 1], elapsed_time);
4691             moveList[moveNum - 1][0] = NULLCHAR;
4692             fromX = fromY = toX = toY = -1;
4693           }
4694         }
4695   if (appData.debugMode) {
4696     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4697     setbuf(debugFP, NULL);
4698   }
4699
4700 #if ZIPPY
4701         /* Send move to chess program (BEFORE animating it). */
4702         if (appData.zippyPlay && !newGame && newMove &&
4703            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4704
4705             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4706                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4707                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4708                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4709                             move_str);
4710                     DisplayError(str, 0);
4711                 } else {
4712                     if (first.sendTime) {
4713                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4714                     }
4715                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4716                     if (firstMove && !bookHit) {
4717                         firstMove = FALSE;
4718                         if (first.useColors) {
4719                           SendToProgram(gameMode == IcsPlayingWhite ?
4720                                         "white\ngo\n" :
4721                                         "black\ngo\n", &first);
4722                         } else {
4723                           SendToProgram("go\n", &first);
4724                         }
4725                         first.maybeThinking = TRUE;
4726                     }
4727                 }
4728             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4729               if (moveList[moveNum - 1][0] == NULLCHAR) {
4730                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4731                 DisplayError(str, 0);
4732               } else {
4733                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4734                 SendMoveToProgram(moveNum - 1, &first);
4735               }
4736             }
4737         }
4738 #endif
4739     }
4740
4741     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4742         /* If move comes from a remote source, animate it.  If it
4743            isn't remote, it will have already been animated. */
4744         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4745             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4746         }
4747         if (!pausing && appData.highlightLastMove) {
4748             SetHighlights(fromX, fromY, toX, toY);
4749         }
4750     }
4751
4752     /* Start the clocks */
4753     whiteFlag = blackFlag = FALSE;
4754     appData.clockMode = !(basetime == 0 && increment == 0);
4755     if (ticking == 0) {
4756       ics_clock_paused = TRUE;
4757       StopClocks();
4758     } else if (ticking == 1) {
4759       ics_clock_paused = FALSE;
4760     }
4761     if (gameMode == IcsIdle ||
4762         relation == RELATION_OBSERVING_STATIC ||
4763         relation == RELATION_EXAMINING ||
4764         ics_clock_paused)
4765       DisplayBothClocks();
4766     else
4767       StartClocks();
4768
4769     /* Display opponents and material strengths */
4770     if (gameInfo.variant != VariantBughouse &&
4771         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4772         if (tinyLayout || smallLayout) {
4773             if(gameInfo.variant == VariantNormal)
4774               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4775                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4776                     basetime, increment);
4777             else
4778               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4779                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4780                     basetime, increment, (int) gameInfo.variant);
4781         } else {
4782             if(gameInfo.variant == VariantNormal)
4783               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4784                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4785                     basetime, increment);
4786             else
4787               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4788                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4789                     basetime, increment, VariantName(gameInfo.variant));
4790         }
4791         DisplayTitle(str);
4792   if (appData.debugMode) {
4793     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4794   }
4795     }
4796
4797
4798     /* Display the board */
4799     if (!pausing && !appData.noGUI) {
4800
4801       if (appData.premove)
4802           if (!gotPremove ||
4803              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4804              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4805               ClearPremoveHighlights();
4806
4807       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4808         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4809       DrawPosition(j, boards[currentMove]);
4810
4811       DisplayMove(moveNum - 1);
4812       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4813             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4814               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4815         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4816       }
4817     }
4818
4819     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4820 #if ZIPPY
4821     if(bookHit) { // [HGM] book: simulate book reply
4822         static char bookMove[MSG_SIZ]; // a bit generous?
4823
4824         programStats.nodes = programStats.depth = programStats.time =
4825         programStats.score = programStats.got_only_move = 0;
4826         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4827
4828         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4829         strcat(bookMove, bookHit);
4830         HandleMachineMove(bookMove, &first);
4831     }
4832 #endif
4833 }
4834
4835 void
4836 GetMoveListEvent()
4837 {
4838     char buf[MSG_SIZ];
4839     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4840         ics_getting_history = H_REQUESTED;
4841         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4842         SendToICS(buf);
4843     }
4844 }
4845
4846 void
4847 AnalysisPeriodicEvent(force)
4848      int force;
4849 {
4850     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4851          && !force) || !appData.periodicUpdates)
4852       return;
4853
4854     /* Send . command to Crafty to collect stats */
4855     SendToProgram(".\n", &first);
4856
4857     /* Don't send another until we get a response (this makes
4858        us stop sending to old Crafty's which don't understand
4859        the "." command (sending illegal cmds resets node count & time,
4860        which looks bad)) */
4861     programStats.ok_to_send = 0;
4862 }
4863
4864 void ics_update_width(new_width)
4865         int new_width;
4866 {
4867         ics_printf("set width %d\n", new_width);
4868 }
4869
4870 void
4871 SendMoveToProgram(moveNum, cps)
4872      int moveNum;
4873      ChessProgramState *cps;
4874 {
4875     char buf[MSG_SIZ];
4876
4877     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4878         // null move in variant where engine does not understand it (for analysis purposes)
4879         SendBoard(cps, moveNum + 1); // send position after move in stead.
4880         return;
4881     }
4882     if (cps->useUsermove) {
4883       SendToProgram("usermove ", cps);
4884     }
4885     if (cps->useSAN) {
4886       char *space;
4887       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4888         int len = space - parseList[moveNum];
4889         memcpy(buf, parseList[moveNum], len);
4890         buf[len++] = '\n';
4891         buf[len] = NULLCHAR;
4892       } else {
4893         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4894       }
4895       SendToProgram(buf, cps);
4896     } else {
4897       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4898         AlphaRank(moveList[moveNum], 4);
4899         SendToProgram(moveList[moveNum], cps);
4900         AlphaRank(moveList[moveNum], 4); // and back
4901       } else
4902       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4903        * the engine. It would be nice to have a better way to identify castle
4904        * moves here. */
4905       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4906                                                                          && cps->useOOCastle) {
4907         int fromX = moveList[moveNum][0] - AAA;
4908         int fromY = moveList[moveNum][1] - ONE;
4909         int toX = moveList[moveNum][2] - AAA;
4910         int toY = moveList[moveNum][3] - ONE;
4911         if((boards[moveNum][fromY][fromX] == WhiteKing
4912             && boards[moveNum][toY][toX] == WhiteRook)
4913            || (boards[moveNum][fromY][fromX] == BlackKing
4914                && boards[moveNum][toY][toX] == BlackRook)) {
4915           if(toX > fromX) SendToProgram("O-O\n", cps);
4916           else SendToProgram("O-O-O\n", cps);
4917         }
4918         else SendToProgram(moveList[moveNum], cps);
4919       } else
4920       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4921         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4922           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4923           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4924                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4925         } else
4926           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4927                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4928         SendToProgram(buf, cps);
4929       }
4930       else SendToProgram(moveList[moveNum], cps);
4931       /* End of additions by Tord */
4932     }
4933
4934     /* [HGM] setting up the opening has brought engine in force mode! */
4935     /*       Send 'go' if we are in a mode where machine should play. */
4936     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4937         (gameMode == TwoMachinesPlay   ||
4938 #if ZIPPY
4939          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4940 #endif
4941          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4942         SendToProgram("go\n", cps);
4943   if (appData.debugMode) {
4944     fprintf(debugFP, "(extra)\n");
4945   }
4946     }
4947     setboardSpoiledMachineBlack = 0;
4948 }
4949
4950 void
4951 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4952      ChessMove moveType;
4953      int fromX, fromY, toX, toY;
4954      char promoChar;
4955 {
4956     char user_move[MSG_SIZ];
4957     char suffix[4];
4958
4959     if(gameInfo.variant == VariantSChess && promoChar) {
4960         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4961         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4962     } else suffix[0] = NULLCHAR;
4963
4964     switch (moveType) {
4965       default:
4966         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4967                 (int)moveType, fromX, fromY, toX, toY);
4968         DisplayError(user_move + strlen("say "), 0);
4969         break;
4970       case WhiteKingSideCastle:
4971       case BlackKingSideCastle:
4972       case WhiteQueenSideCastleWild:
4973       case BlackQueenSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteHSideCastleFR:
4976       case BlackHSideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4979         break;
4980       case WhiteQueenSideCastle:
4981       case BlackQueenSideCastle:
4982       case WhiteKingSideCastleWild:
4983       case BlackKingSideCastleWild:
4984       /* PUSH Fabien */
4985       case WhiteASideCastleFR:
4986       case BlackASideCastleFR:
4987       /* POP Fabien */
4988         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4989         break;
4990       case WhiteNonPromotion:
4991       case BlackNonPromotion:
4992         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993         break;
4994       case WhitePromotion:
4995       case BlackPromotion:
4996         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteFerz));
5000         else if(gameInfo.variant == VariantGreat)
5001           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 PieceToChar(WhiteMan));
5004         else
5005           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5006                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5007                 promoChar);
5008         break;
5009       case WhiteDrop:
5010       case BlackDrop:
5011       drop:
5012         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5013                  ToUpper(PieceToChar((ChessSquare) fromX)),
5014                  AAA + toX, ONE + toY);
5015         break;
5016       case IllegalMove:  /* could be a variant we don't quite understand */
5017         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5018       case NormalMove:
5019       case WhiteCapturesEnPassant:
5020       case BlackCapturesEnPassant:
5021         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5023         break;
5024     }
5025     SendToICS(user_move);
5026     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5027         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5028 }
5029
5030 void
5031 UploadGameEvent()
5032 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5033     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5034     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5035     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5036       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5037       return;
5038     }
5039     if(gameMode != IcsExamining) { // is this ever not the case?
5040         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5041
5042         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5043           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5044         } else { // on FICS we must first go to general examine mode
5045           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5046         }
5047         if(gameInfo.variant != VariantNormal) {
5048             // try figure out wild number, as xboard names are not always valid on ICS
5049             for(i=1; i<=36; i++) {
5050               snprintf(buf, MSG_SIZ, "wild/%d", i);
5051                 if(StringToVariant(buf) == gameInfo.variant) break;
5052             }
5053             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5054             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5055             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5056         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5057         SendToICS(ics_prefix);
5058         SendToICS(buf);
5059         if(startedFromSetupPosition || backwardMostMove != 0) {
5060           fen = PositionToFEN(backwardMostMove, NULL);
5061           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5062             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5063             SendToICS(buf);
5064           } else { // FICS: everything has to set by separate bsetup commands
5065             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5066             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5067             SendToICS(buf);
5068             if(!WhiteOnMove(backwardMostMove)) {
5069                 SendToICS("bsetup tomove black\n");
5070             }
5071             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5072             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5073             SendToICS(buf);
5074             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5075             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5076             SendToICS(buf);
5077             i = boards[backwardMostMove][EP_STATUS];
5078             if(i >= 0) { // set e.p.
5079               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5080                 SendToICS(buf);
5081             }
5082             bsetup++;
5083           }
5084         }
5085       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5086             SendToICS("bsetup done\n"); // switch to normal examining.
5087     }
5088     for(i = backwardMostMove; i<last; i++) {
5089         char buf[20];
5090         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5091         SendToICS(buf);
5092     }
5093     SendToICS(ics_prefix);
5094     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5095 }
5096
5097 void
5098 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5099      int rf, ff, rt, ft;
5100      char promoChar;
5101      char move[7];
5102 {
5103     if (rf == DROP_RANK) {
5104       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5105       sprintf(move, "%c@%c%c\n",
5106                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5107     } else {
5108         if (promoChar == 'x' || promoChar == NULLCHAR) {
5109           sprintf(move, "%c%c%c%c\n",
5110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5111         } else {
5112             sprintf(move, "%c%c%c%c%c\n",
5113                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5114         }
5115     }
5116 }
5117
5118 void
5119 ProcessICSInitScript(f)
5120      FILE *f;
5121 {
5122     char buf[MSG_SIZ];
5123
5124     while (fgets(buf, MSG_SIZ, f)) {
5125         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5126     }
5127
5128     fclose(f);
5129 }
5130
5131
5132 static int lastX, lastY, selectFlag, dragging;
5133
5134 void
5135 Sweep(int step)
5136 {
5137     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5138     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5139     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5140     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5141     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5142     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5143     do {
5144         promoSweep -= step;
5145         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5146         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5147         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5148         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5149         if(!step) step = -1;
5150     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5151             appData.testLegality && (promoSweep == king ||
5152             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5153     ChangeDragPiece(promoSweep);
5154 }
5155
5156 int PromoScroll(int x, int y)
5157 {
5158   int step = 0;
5159
5160   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5161   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5162   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5163   if(!step) return FALSE;
5164   lastX = x; lastY = y;
5165   if((promoSweep < BlackPawn) == flipView) step = -step;
5166   if(step > 0) selectFlag = 1;
5167   if(!selectFlag) Sweep(step);
5168   return FALSE;
5169 }
5170
5171 void
5172 NextPiece(int step)
5173 {
5174     ChessSquare piece = boards[currentMove][toY][toX];
5175     do {
5176         pieceSweep -= step;
5177         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5178         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5179         if(!step) step = -1;
5180     } while(PieceToChar(pieceSweep) == '.');
5181     boards[currentMove][toY][toX] = pieceSweep;
5182     DrawPosition(FALSE, boards[currentMove]);
5183     boards[currentMove][toY][toX] = piece;
5184 }
5185 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5186 void
5187 AlphaRank(char *move, int n)
5188 {
5189 //    char *p = move, c; int x, y;
5190
5191     if (appData.debugMode) {
5192         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5193     }
5194
5195     if(move[1]=='*' &&
5196        move[2]>='0' && move[2]<='9' &&
5197        move[3]>='a' && move[3]<='x'    ) {
5198         move[1] = '@';
5199         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5201     } else
5202     if(move[0]>='0' && move[0]<='9' &&
5203        move[1]>='a' && move[1]<='x' &&
5204        move[2]>='0' && move[2]<='9' &&
5205        move[3]>='a' && move[3]<='x'    ) {
5206         /* input move, Shogi -> normal */
5207         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5208         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5209         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5210         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5211     } else
5212     if(move[1]=='@' &&
5213        move[3]>='0' && move[3]<='9' &&
5214        move[2]>='a' && move[2]<='x'    ) {
5215         move[1] = '*';
5216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5218     } else
5219     if(
5220        move[0]>='a' && move[0]<='x' &&
5221        move[3]>='0' && move[3]<='9' &&
5222        move[2]>='a' && move[2]<='x'    ) {
5223          /* output move, normal -> Shogi */
5224         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5225         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5226         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5227         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5228         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5229     }
5230     if (appData.debugMode) {
5231         fprintf(debugFP, "   out = '%s'\n", move);
5232     }
5233 }
5234
5235 char yy_textstr[8000];
5236
5237 /* Parser for moves from gnuchess, ICS, or user typein box */
5238 Boolean
5239 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5240      char *move;
5241      int moveNum;
5242      ChessMove *moveType;
5243      int *fromX, *fromY, *toX, *toY;
5244      char *promoChar;
5245 {
5246     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5247
5248     switch (*moveType) {
5249       case WhitePromotion:
5250       case BlackPromotion:
5251       case WhiteNonPromotion:
5252       case BlackNonPromotion:
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256       case WhiteKingSideCastle:
5257       case WhiteQueenSideCastle:
5258       case BlackKingSideCastle:
5259       case BlackQueenSideCastle:
5260       case WhiteKingSideCastleWild:
5261       case WhiteQueenSideCastleWild:
5262       case BlackKingSideCastleWild:
5263       case BlackQueenSideCastleWild:
5264       /* Code added by Tord: */
5265       case WhiteHSideCastleFR:
5266       case WhiteASideCastleFR:
5267       case BlackHSideCastleFR:
5268       case BlackASideCastleFR:
5269       /* End of code added by Tord */
5270       case IllegalMove:         /* bug or odd chess variant */
5271         *fromX = currentMoveString[0] - AAA;
5272         *fromY = currentMoveString[1] - ONE;
5273         *toX = currentMoveString[2] - AAA;
5274         *toY = currentMoveString[3] - ONE;
5275         *promoChar = currentMoveString[4];
5276         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5277             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5278     if (appData.debugMode) {
5279         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5280     }
5281             *fromX = *fromY = *toX = *toY = 0;
5282             return FALSE;
5283         }
5284         if (appData.testLegality) {
5285           return (*moveType != IllegalMove);
5286         } else {
5287           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5288                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5289         }
5290
5291       case WhiteDrop:
5292       case BlackDrop:
5293         *fromX = *moveType == WhiteDrop ?
5294           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5295           (int) CharToPiece(ToLower(currentMoveString[0]));
5296         *fromY = DROP_RANK;
5297         *toX = currentMoveString[2] - AAA;
5298         *toY = currentMoveString[3] - ONE;
5299         *promoChar = NULLCHAR;
5300         return TRUE;
5301
5302       case AmbiguousMove:
5303       case ImpossibleMove:
5304       case EndOfFile:
5305       case ElapsedTime:
5306       case Comment:
5307       case PGNTag:
5308       case NAG:
5309       case WhiteWins:
5310       case BlackWins:
5311       case GameIsDrawn:
5312       default:
5313     if (appData.debugMode) {
5314         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5315     }
5316         /* bug? */
5317         *fromX = *fromY = *toX = *toY = 0;
5318         *promoChar = NULLCHAR;
5319         return FALSE;
5320     }
5321 }
5322
5323 Boolean pushed = FALSE;
5324 char *lastParseAttempt;
5325
5326 void
5327 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5328 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5329   int fromX, fromY, toX, toY; char promoChar;
5330   ChessMove moveType;
5331   Boolean valid;
5332   int nr = 0;
5333
5334   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5335     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5336     pushed = TRUE;
5337   }
5338   endPV = forwardMostMove;
5339   do {
5340     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5341     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5342     lastParseAttempt = pv;
5343     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5344 if(appData.debugMode){
5345 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);
5346 }
5347     if(!valid && nr == 0 &&
5348        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5349         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5350         // Hande case where played move is different from leading PV move
5351         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5352         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5353         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5354         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5355           endPV += 2; // if position different, keep this
5356           moveList[endPV-1][0] = fromX + AAA;
5357           moveList[endPV-1][1] = fromY + ONE;
5358           moveList[endPV-1][2] = toX + AAA;
5359           moveList[endPV-1][3] = toY + ONE;
5360           parseList[endPV-1][0] = NULLCHAR;
5361           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5362         }
5363       }
5364     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5365     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5366     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5367     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5368         valid++; // allow comments in PV
5369         continue;
5370     }
5371     nr++;
5372     if(endPV+1 > framePtr) break; // no space, truncate
5373     if(!valid) break;
5374     endPV++;
5375     CopyBoard(boards[endPV], boards[endPV-1]);
5376     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5377     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5378     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5379     CoordsToAlgebraic(boards[endPV - 1],
5380                              PosFlags(endPV - 1),
5381                              fromY, fromX, toY, toX, promoChar,
5382                              parseList[endPV - 1]);
5383   } while(valid);
5384   if(atEnd == 2) return; // used hidden, for PV conversion
5385   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5386   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5387   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5388                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5389   DrawPosition(TRUE, boards[currentMove]);
5390 }
5391
5392 int
5393 MultiPV(ChessProgramState *cps)
5394 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5395         int i;
5396         for(i=0; i<cps->nrOptions; i++)
5397             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5398                 return i;
5399         return -1;
5400 }
5401
5402 Boolean
5403 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5404 {
5405         int startPV, multi, lineStart, origIndex = index;
5406         char *p, buf2[MSG_SIZ];
5407
5408         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5409         lastX = x; lastY = y;
5410         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5411         lineStart = startPV = index;
5412         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5413         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5414         index = startPV;
5415         do{ while(buf[index] && buf[index] != '\n') index++;
5416         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5417         buf[index] = 0;
5418         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5419                 int n = first.option[multi].value;
5420                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5421                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5422                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5423                 first.option[multi].value = n;
5424                 *start = *end = 0;
5425                 return FALSE;
5426         }
5427         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5428         *start = startPV; *end = index-1;
5429         return TRUE;
5430 }
5431
5432 char *
5433 PvToSAN(char *pv)
5434 {
5435         static char buf[10*MSG_SIZ];
5436         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5437         *buf = NULLCHAR;
5438         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5439         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5440         for(i = forwardMostMove; i<endPV; i++){
5441             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5442             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5443             k += strlen(buf+k);
5444         }
5445         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5446         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5447         endPV = savedEnd;
5448         return buf;
5449 }
5450
5451 Boolean
5452 LoadPV(int x, int y)
5453 { // called on right mouse click to load PV
5454   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5455   lastX = x; lastY = y;
5456   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5457   return TRUE;
5458 }
5459
5460 void
5461 UnLoadPV()
5462 {
5463   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5464   if(endPV < 0) return;
5465   endPV = -1;
5466   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5467         Boolean saveAnimate = appData.animate;
5468         if(pushed) {
5469             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5470                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5471             } else storedGames--; // abandon shelved tail of original game
5472         }
5473         pushed = FALSE;
5474         forwardMostMove = currentMove;
5475         currentMove = oldFMM;
5476         appData.animate = FALSE;
5477         ToNrEvent(forwardMostMove);
5478         appData.animate = saveAnimate;
5479   }
5480   currentMove = forwardMostMove;
5481   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5482   ClearPremoveHighlights();
5483   DrawPosition(TRUE, boards[currentMove]);
5484 }
5485
5486 void
5487 MovePV(int x, int y, int h)
5488 { // step through PV based on mouse coordinates (called on mouse move)
5489   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5490
5491   // we must somehow check if right button is still down (might be released off board!)
5492   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5493   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5494   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5495   if(!step) return;
5496   lastX = x; lastY = y;
5497
5498   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5499   if(endPV < 0) return;
5500   if(y < margin) step = 1; else
5501   if(y > h - margin) step = -1;
5502   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5503   currentMove += step;
5504   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5505   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5506                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5507   DrawPosition(FALSE, boards[currentMove]);
5508 }
5509
5510
5511 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5512 // All positions will have equal probability, but the current method will not provide a unique
5513 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5514 #define DARK 1
5515 #define LITE 2
5516 #define ANY 3
5517
5518 int squaresLeft[4];
5519 int piecesLeft[(int)BlackPawn];
5520 int seed, nrOfShuffles;
5521
5522 void GetPositionNumber()
5523 {       // sets global variable seed
5524         int i;
5525
5526         seed = appData.defaultFrcPosition;
5527         if(seed < 0) { // randomize based on time for negative FRC position numbers
5528                 for(i=0; i<50; i++) seed += random();
5529                 seed = random() ^ random() >> 8 ^ random() << 8;
5530                 if(seed<0) seed = -seed;
5531         }
5532 }
5533
5534 int put(Board board, int pieceType, int rank, int n, int shade)
5535 // put the piece on the (n-1)-th empty squares of the given shade
5536 {
5537         int i;
5538
5539         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5540                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5541                         board[rank][i] = (ChessSquare) pieceType;
5542                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5543                         squaresLeft[ANY]--;
5544                         piecesLeft[pieceType]--;
5545                         return i;
5546                 }
5547         }
5548         return -1;
5549 }
5550
5551
5552 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5553 // calculate where the next piece goes, (any empty square), and put it there
5554 {
5555         int i;
5556
5557         i = seed % squaresLeft[shade];
5558         nrOfShuffles *= squaresLeft[shade];
5559         seed /= squaresLeft[shade];
5560         put(board, pieceType, rank, i, shade);
5561 }
5562
5563 void AddTwoPieces(Board board, int pieceType, int rank)
5564 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5565 {
5566         int i, n=squaresLeft[ANY], j=n-1, k;
5567
5568         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5569         i = seed % k;  // pick one
5570         nrOfShuffles *= k;
5571         seed /= k;
5572         while(i >= j) i -= j--;
5573         j = n - 1 - j; i += j;
5574         put(board, pieceType, rank, j, ANY);
5575         put(board, pieceType, rank, i, ANY);
5576 }
5577
5578 void SetUpShuffle(Board board, int number)
5579 {
5580         int i, p, first=1;
5581
5582         GetPositionNumber(); nrOfShuffles = 1;
5583
5584         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5585         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5586         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5587
5588         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5589
5590         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5591             p = (int) board[0][i];
5592             if(p < (int) BlackPawn) piecesLeft[p] ++;
5593             board[0][i] = EmptySquare;
5594         }
5595
5596         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5597             // shuffles restricted to allow normal castling put KRR first
5598             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5599                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5600             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5601                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5602             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5603                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5604             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5605                 put(board, WhiteRook, 0, 0, ANY);
5606             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5607         }
5608
5609         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5610             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5611             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5612                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5613                 while(piecesLeft[p] >= 2) {
5614                     AddOnePiece(board, p, 0, LITE);
5615                     AddOnePiece(board, p, 0, DARK);
5616                 }
5617                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5618             }
5619
5620         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5621             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5622             // but we leave King and Rooks for last, to possibly obey FRC restriction
5623             if(p == (int)WhiteRook) continue;
5624             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5625             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5626         }
5627
5628         // now everything is placed, except perhaps King (Unicorn) and Rooks
5629
5630         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5631             // Last King gets castling rights
5632             while(piecesLeft[(int)WhiteUnicorn]) {
5633                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5634                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5635             }
5636
5637             while(piecesLeft[(int)WhiteKing]) {
5638                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5639                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5640             }
5641
5642
5643         } else {
5644             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5645             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5646         }
5647
5648         // Only Rooks can be left; simply place them all
5649         while(piecesLeft[(int)WhiteRook]) {
5650                 i = put(board, WhiteRook, 0, 0, ANY);
5651                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5652                         if(first) {
5653                                 first=0;
5654                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5655                         }
5656                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5657                 }
5658         }
5659         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5660             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5661         }
5662
5663         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5664 }
5665
5666 int SetCharTable( char *table, const char * map )
5667 /* [HGM] moved here from winboard.c because of its general usefulness */
5668 /*       Basically a safe strcpy that uses the last character as King */
5669 {
5670     int result = FALSE; int NrPieces;
5671
5672     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5673                     && NrPieces >= 12 && !(NrPieces&1)) {
5674         int i; /* [HGM] Accept even length from 12 to 34 */
5675
5676         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5677         for( i=0; i<NrPieces/2-1; i++ ) {
5678             table[i] = map[i];
5679             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5680         }
5681         table[(int) WhiteKing]  = map[NrPieces/2-1];
5682         table[(int) BlackKing]  = map[NrPieces-1];
5683
5684         result = TRUE;
5685     }
5686
5687     return result;
5688 }
5689
5690 void Prelude(Board board)
5691 {       // [HGM] superchess: random selection of exo-pieces
5692         int i, j, k; ChessSquare p;
5693         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5694
5695         GetPositionNumber(); // use FRC position number
5696
5697         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5698             SetCharTable(pieceToChar, appData.pieceToCharTable);
5699             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5700                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5701         }
5702
5703         j = seed%4;                 seed /= 4;
5704         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%3 + (seed%3 >= j); seed /= 3;
5708         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%3;                 seed /= 3;
5712         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5713         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5714         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5715         j = seed%2 + (seed%2 >= j); seed /= 2;
5716         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5717         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5718         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5719         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5720         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5721         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5722         put(board, exoPieces[0],    0, 0, ANY);
5723         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5724 }
5725
5726 void
5727 InitPosition(redraw)
5728      int redraw;
5729 {
5730     ChessSquare (* pieces)[BOARD_FILES];
5731     int i, j, pawnRow, overrule,
5732     oldx = gameInfo.boardWidth,
5733     oldy = gameInfo.boardHeight,
5734     oldh = gameInfo.holdingsWidth;
5735     static int oldv;
5736
5737     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5738
5739     /* [AS] Initialize pv info list [HGM] and game status */
5740     {
5741         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5742             pvInfoList[i].depth = 0;
5743             boards[i][EP_STATUS] = EP_NONE;
5744             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5745         }
5746
5747         initialRulePlies = 0; /* 50-move counter start */
5748
5749         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5750         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5751     }
5752
5753
5754     /* [HGM] logic here is completely changed. In stead of full positions */
5755     /* the initialized data only consist of the two backranks. The switch */
5756     /* selects which one we will use, which is than copied to the Board   */
5757     /* initialPosition, which for the rest is initialized by Pawns and    */
5758     /* empty squares. This initial position is then copied to boards[0],  */
5759     /* possibly after shuffling, so that it remains available.            */
5760
5761     gameInfo.holdingsWidth = 0; /* default board sizes */
5762     gameInfo.boardWidth    = 8;
5763     gameInfo.boardHeight   = 8;
5764     gameInfo.holdingsSize  = 0;
5765     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5766     for(i=0; i<BOARD_FILES-2; i++)
5767       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5768     initialPosition[EP_STATUS] = EP_NONE;
5769     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5770     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5771          SetCharTable(pieceNickName, appData.pieceNickNames);
5772     else SetCharTable(pieceNickName, "............");
5773     pieces = FIDEArray;
5774
5775     switch (gameInfo.variant) {
5776     case VariantFischeRandom:
5777       shuffleOpenings = TRUE;
5778     default:
5779       break;
5780     case VariantShatranj:
5781       pieces = ShatranjArray;
5782       nrCastlingRights = 0;
5783       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5784       break;
5785     case VariantMakruk:
5786       pieces = makrukArray;
5787       nrCastlingRights = 0;
5788       startedFromSetupPosition = TRUE;
5789       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5790       break;
5791     case VariantTwoKings:
5792       pieces = twoKingsArray;
5793       break;
5794     case VariantGrand:
5795       pieces = GrandArray;
5796       nrCastlingRights = 0;
5797       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5798       gameInfo.boardWidth = 10;
5799       gameInfo.boardHeight = 10;
5800       gameInfo.holdingsSize = 7;
5801       break;
5802     case VariantCapaRandom:
5803       shuffleOpenings = TRUE;
5804     case VariantCapablanca:
5805       pieces = CapablancaArray;
5806       gameInfo.boardWidth = 10;
5807       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808       break;
5809     case VariantGothic:
5810       pieces = GothicArray;
5811       gameInfo.boardWidth = 10;
5812       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5813       break;
5814     case VariantSChess:
5815       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5816       gameInfo.holdingsSize = 7;
5817       break;
5818     case VariantJanus:
5819       pieces = JanusArray;
5820       gameInfo.boardWidth = 10;
5821       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5822       nrCastlingRights = 6;
5823         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5824         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5825         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5826         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5827         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5828         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5829       break;
5830     case VariantFalcon:
5831       pieces = FalconArray;
5832       gameInfo.boardWidth = 10;
5833       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5834       break;
5835     case VariantXiangqi:
5836       pieces = XiangqiArray;
5837       gameInfo.boardWidth  = 9;
5838       gameInfo.boardHeight = 10;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5841       break;
5842     case VariantShogi:
5843       pieces = ShogiArray;
5844       gameInfo.boardWidth  = 9;
5845       gameInfo.boardHeight = 9;
5846       gameInfo.holdingsSize = 7;
5847       nrCastlingRights = 0;
5848       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5849       break;
5850     case VariantCourier:
5851       pieces = CourierArray;
5852       gameInfo.boardWidth  = 12;
5853       nrCastlingRights = 0;
5854       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5855       break;
5856     case VariantKnightmate:
5857       pieces = KnightmateArray;
5858       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5859       break;
5860     case VariantSpartan:
5861       pieces = SpartanArray;
5862       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5863       break;
5864     case VariantFairy:
5865       pieces = fairyArray;
5866       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5867       break;
5868     case VariantGreat:
5869       pieces = GreatArray;
5870       gameInfo.boardWidth = 10;
5871       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5872       gameInfo.holdingsSize = 8;
5873       break;
5874     case VariantSuper:
5875       pieces = FIDEArray;
5876       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5877       gameInfo.holdingsSize = 8;
5878       startedFromSetupPosition = TRUE;
5879       break;
5880     case VariantCrazyhouse:
5881     case VariantBughouse:
5882       pieces = FIDEArray;
5883       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5884       gameInfo.holdingsSize = 5;
5885       break;
5886     case VariantWildCastle:
5887       pieces = FIDEArray;
5888       /* !!?shuffle with kings guaranteed to be on d or e file */
5889       shuffleOpenings = 1;
5890       break;
5891     case VariantNoCastle:
5892       pieces = FIDEArray;
5893       nrCastlingRights = 0;
5894       /* !!?unconstrained back-rank shuffle */
5895       shuffleOpenings = 1;
5896       break;
5897     }
5898
5899     overrule = 0;
5900     if(appData.NrFiles >= 0) {
5901         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5902         gameInfo.boardWidth = appData.NrFiles;
5903     }
5904     if(appData.NrRanks >= 0) {
5905         gameInfo.boardHeight = appData.NrRanks;
5906     }
5907     if(appData.holdingsSize >= 0) {
5908         i = appData.holdingsSize;
5909         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5910         gameInfo.holdingsSize = i;
5911     }
5912     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5913     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5914         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5915
5916     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5917     if(pawnRow < 1) pawnRow = 1;
5918     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5919
5920     /* User pieceToChar list overrules defaults */
5921     if(appData.pieceToCharTable != NULL)
5922         SetCharTable(pieceToChar, appData.pieceToCharTable);
5923
5924     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5925
5926         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5927             s = (ChessSquare) 0; /* account holding counts in guard band */
5928         for( i=0; i<BOARD_HEIGHT; i++ )
5929             initialPosition[i][j] = s;
5930
5931         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5932         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5933         initialPosition[pawnRow][j] = WhitePawn;
5934         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5935         if(gameInfo.variant == VariantXiangqi) {
5936             if(j&1) {
5937                 initialPosition[pawnRow][j] =
5938                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5939                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5940                    initialPosition[2][j] = WhiteCannon;
5941                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5942                 }
5943             }
5944         }
5945         if(gameInfo.variant == VariantGrand) {
5946             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5947                initialPosition[0][j] = WhiteRook;
5948                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5949             }
5950         }
5951         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5952     }
5953     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5954
5955             j=BOARD_LEFT+1;
5956             initialPosition[1][j] = WhiteBishop;
5957             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5958             j=BOARD_RGHT-2;
5959             initialPosition[1][j] = WhiteRook;
5960             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5961     }
5962
5963     if( nrCastlingRights == -1) {
5964         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5965         /*       This sets default castling rights from none to normal corners   */
5966         /* Variants with other castling rights must set them themselves above    */
5967         nrCastlingRights = 6;
5968
5969         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5970         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5971         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5972         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5973         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5974         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5975      }
5976
5977      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5978      if(gameInfo.variant == VariantGreat) { // promotion commoners
5979         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5981         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5983      }
5984      if( gameInfo.variant == VariantSChess ) {
5985       initialPosition[1][0] = BlackMarshall;
5986       initialPosition[2][0] = BlackAngel;
5987       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5988       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5989       initialPosition[1][1] = initialPosition[2][1] = 
5990       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5991      }
5992   if (appData.debugMode) {
5993     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5994   }
5995     if(shuffleOpenings) {
5996         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5997         startedFromSetupPosition = TRUE;
5998     }
5999     if(startedFromPositionFile) {
6000       /* [HGM] loadPos: use PositionFile for every new game */
6001       CopyBoard(initialPosition, filePosition);
6002       for(i=0; i<nrCastlingRights; i++)
6003           initialRights[i] = filePosition[CASTLING][i];
6004       startedFromSetupPosition = TRUE;
6005     }
6006
6007     CopyBoard(boards[0], initialPosition);
6008
6009     if(oldx != gameInfo.boardWidth ||
6010        oldy != gameInfo.boardHeight ||
6011        oldv != gameInfo.variant ||
6012        oldh != gameInfo.holdingsWidth
6013                                          )
6014             InitDrawingSizes(-2 ,0);
6015
6016     oldv = gameInfo.variant;
6017     if (redraw)
6018       DrawPosition(TRUE, boards[currentMove]);
6019 }
6020
6021 void
6022 SendBoard(cps, moveNum)
6023      ChessProgramState *cps;
6024      int moveNum;
6025 {
6026     char message[MSG_SIZ];
6027
6028     if (cps->useSetboard) {
6029       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6030       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6031       SendToProgram(message, cps);
6032       free(fen);
6033
6034     } else {
6035       ChessSquare *bp;
6036       int i, j, left=0, right=BOARD_WIDTH;
6037       /* Kludge to set black to move, avoiding the troublesome and now
6038        * deprecated "black" command.
6039        */
6040       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6041         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6042
6043       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6044
6045       SendToProgram("edit\n", cps);
6046       SendToProgram("#\n", cps);
6047       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6048         bp = &boards[moveNum][i][left];
6049         for (j = left; j < right; j++, bp++) {
6050           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6051           if ((int) *bp < (int) BlackPawn) {
6052             if(j == BOARD_RGHT+1)
6053                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6054             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6055             if(message[0] == '+' || message[0] == '~') {
6056               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6057                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6058                         AAA + j, ONE + i);
6059             }
6060             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6061                 message[1] = BOARD_RGHT   - 1 - j + '1';
6062                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6063             }
6064             SendToProgram(message, cps);
6065           }
6066         }
6067       }
6068
6069       SendToProgram("c\n", cps);
6070       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6071         bp = &boards[moveNum][i][left];
6072         for (j = left; j < right; j++, bp++) {
6073           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6074           if (((int) *bp != (int) EmptySquare)
6075               && ((int) *bp >= (int) BlackPawn)) {
6076             if(j == BOARD_LEFT-2)
6077                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6078             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6079                     AAA + j, ONE + i);
6080             if(message[0] == '+' || message[0] == '~') {
6081               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6082                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6083                         AAA + j, ONE + i);
6084             }
6085             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6086                 message[1] = BOARD_RGHT   - 1 - j + '1';
6087                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6088             }
6089             SendToProgram(message, cps);
6090           }
6091         }
6092       }
6093
6094       SendToProgram(".\n", cps);
6095     }
6096     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6097 }
6098
6099 ChessSquare
6100 DefaultPromoChoice(int white)
6101 {
6102     ChessSquare result;
6103     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6104         result = WhiteFerz; // no choice
6105     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6106         result= WhiteKing; // in Suicide Q is the last thing we want
6107     else if(gameInfo.variant == VariantSpartan)
6108         result = white ? WhiteQueen : WhiteAngel;
6109     else result = WhiteQueen;
6110     if(!white) result = WHITE_TO_BLACK result;
6111     return result;
6112 }
6113
6114 static int autoQueen; // [HGM] oneclick
6115
6116 int
6117 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6118 {
6119     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6120     /* [HGM] add Shogi promotions */
6121     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6122     ChessSquare piece;
6123     ChessMove moveType;
6124     Boolean premove;
6125
6126     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6127     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6128
6129     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6130       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6131         return FALSE;
6132
6133     piece = boards[currentMove][fromY][fromX];
6134     if(gameInfo.variant == VariantShogi) {
6135         promotionZoneSize = BOARD_HEIGHT/3;
6136         highestPromotingPiece = (int)WhiteFerz;
6137     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6138         promotionZoneSize = 3;
6139     }
6140
6141     // Treat Lance as Pawn when it is not representing Amazon
6142     if(gameInfo.variant != VariantSuper) {
6143         if(piece == WhiteLance) piece = WhitePawn; else
6144         if(piece == BlackLance) piece = BlackPawn;
6145     }
6146
6147     // next weed out all moves that do not touch the promotion zone at all
6148     if((int)piece >= BlackPawn) {
6149         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6150              return FALSE;
6151         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6152     } else {
6153         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6154            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6155     }
6156
6157     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6158
6159     // weed out mandatory Shogi promotions
6160     if(gameInfo.variant == VariantShogi) {
6161         if(piece >= BlackPawn) {
6162             if(toY == 0 && piece == BlackPawn ||
6163                toY == 0 && piece == BlackQueen ||
6164                toY <= 1 && piece == BlackKnight) {
6165                 *promoChoice = '+';
6166                 return FALSE;
6167             }
6168         } else {
6169             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6170                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6171                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6172                 *promoChoice = '+';
6173                 return FALSE;
6174             }
6175         }
6176     }
6177
6178     // weed out obviously illegal Pawn moves
6179     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6180         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6181         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6182         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6183         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6184         // note we are not allowed to test for valid (non-)capture, due to premove
6185     }
6186
6187     // we either have a choice what to promote to, or (in Shogi) whether to promote
6188     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6189         *promoChoice = PieceToChar(BlackFerz);  // no choice
6190         return FALSE;
6191     }
6192     // no sense asking what we must promote to if it is going to explode...
6193     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6194         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6195         return FALSE;
6196     }
6197     // give caller the default choice even if we will not make it
6198     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6199     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6200     if(        sweepSelect && gameInfo.variant != VariantGreat
6201                            && gameInfo.variant != VariantGrand
6202                            && gameInfo.variant != VariantSuper) return FALSE;
6203     if(autoQueen) return FALSE; // predetermined
6204
6205     // suppress promotion popup on illegal moves that are not premoves
6206     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6207               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6208     if(appData.testLegality && !premove) {
6209         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6210                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6211         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6212             return FALSE;
6213     }
6214
6215     return TRUE;
6216 }
6217
6218 int
6219 InPalace(row, column)
6220      int row, column;
6221 {   /* [HGM] for Xiangqi */
6222     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6223          column < (BOARD_WIDTH + 4)/2 &&
6224          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6225     return FALSE;
6226 }
6227
6228 int
6229 PieceForSquare (x, y)
6230      int x;
6231      int y;
6232 {
6233   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6234      return -1;
6235   else
6236      return boards[currentMove][y][x];
6237 }
6238
6239 int
6240 OKToStartUserMove(x, y)
6241      int x, y;
6242 {
6243     ChessSquare from_piece;
6244     int white_piece;
6245
6246     if (matchMode) return FALSE;
6247     if (gameMode == EditPosition) return TRUE;
6248
6249     if (x >= 0 && y >= 0)
6250       from_piece = boards[currentMove][y][x];
6251     else
6252       from_piece = EmptySquare;
6253
6254     if (from_piece == EmptySquare) return FALSE;
6255
6256     white_piece = (int)from_piece >= (int)WhitePawn &&
6257       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6258
6259     switch (gameMode) {
6260       case AnalyzeFile:
6261       case TwoMachinesPlay:
6262       case EndOfGame:
6263         return FALSE;
6264
6265       case IcsObserving:
6266       case IcsIdle:
6267         return FALSE;
6268
6269       case MachinePlaysWhite:
6270       case IcsPlayingBlack:
6271         if (appData.zippyPlay) return FALSE;
6272         if (white_piece) {
6273             DisplayMoveError(_("You are playing Black"));
6274             return FALSE;
6275         }
6276         break;
6277
6278       case MachinePlaysBlack:
6279       case IcsPlayingWhite:
6280         if (appData.zippyPlay) return FALSE;
6281         if (!white_piece) {
6282             DisplayMoveError(_("You are playing White"));
6283             return FALSE;
6284         }
6285         break;
6286
6287       case PlayFromGameFile:
6288             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6289       case EditGame:
6290         if (!white_piece && WhiteOnMove(currentMove)) {
6291             DisplayMoveError(_("It is White's turn"));
6292             return FALSE;
6293         }
6294         if (white_piece && !WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is Black's turn"));
6296             return FALSE;
6297         }
6298         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6299             /* Editing correspondence game history */
6300             /* Could disallow this or prompt for confirmation */
6301             cmailOldMove = -1;
6302         }
6303         break;
6304
6305       case BeginningOfGame:
6306         if (appData.icsActive) return FALSE;
6307         if (!appData.noChessProgram) {
6308             if (!white_piece) {
6309                 DisplayMoveError(_("You are playing White"));
6310                 return FALSE;
6311             }
6312         }
6313         break;
6314
6315       case Training:
6316         if (!white_piece && WhiteOnMove(currentMove)) {
6317             DisplayMoveError(_("It is White's turn"));
6318             return FALSE;
6319         }
6320         if (white_piece && !WhiteOnMove(currentMove)) {
6321             DisplayMoveError(_("It is Black's turn"));
6322             return FALSE;
6323         }
6324         break;
6325
6326       default:
6327       case IcsExamining:
6328         break;
6329     }
6330     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6331         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6332         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6333         && gameMode != AnalyzeFile && gameMode != Training) {
6334         DisplayMoveError(_("Displayed position is not current"));
6335         return FALSE;
6336     }
6337     return TRUE;
6338 }
6339
6340 Boolean
6341 OnlyMove(int *x, int *y, Boolean captures) {
6342     DisambiguateClosure cl;
6343     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6344     switch(gameMode) {
6345       case MachinePlaysBlack:
6346       case IcsPlayingWhite:
6347       case BeginningOfGame:
6348         if(!WhiteOnMove(currentMove)) return FALSE;
6349         break;
6350       case MachinePlaysWhite:
6351       case IcsPlayingBlack:
6352         if(WhiteOnMove(currentMove)) return FALSE;
6353         break;
6354       case EditGame:
6355         break;
6356       default:
6357         return FALSE;
6358     }
6359     cl.pieceIn = EmptySquare;
6360     cl.rfIn = *y;
6361     cl.ffIn = *x;
6362     cl.rtIn = -1;
6363     cl.ftIn = -1;
6364     cl.promoCharIn = NULLCHAR;
6365     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6366     if( cl.kind == NormalMove ||
6367         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6368         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6369         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6370       fromX = cl.ff;
6371       fromY = cl.rf;
6372       *x = cl.ft;
6373       *y = cl.rt;
6374       return TRUE;
6375     }
6376     if(cl.kind != ImpossibleMove) return FALSE;
6377     cl.pieceIn = EmptySquare;
6378     cl.rfIn = -1;
6379     cl.ffIn = -1;
6380     cl.rtIn = *y;
6381     cl.ftIn = *x;
6382     cl.promoCharIn = NULLCHAR;
6383     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6384     if( cl.kind == NormalMove ||
6385         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6386         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6387         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6388       fromX = cl.ff;
6389       fromY = cl.rf;
6390       *x = cl.ft;
6391       *y = cl.rt;
6392       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6393       return TRUE;
6394     }
6395     return FALSE;
6396 }
6397
6398 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6399 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6400 int lastLoadGameUseList = FALSE;
6401 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6402 ChessMove lastLoadGameStart = EndOfFile;
6403
6404 void
6405 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6406      int fromX, fromY, toX, toY;
6407      int promoChar;
6408 {
6409     ChessMove moveType;
6410     ChessSquare pdown, pup;
6411
6412     /* Check if the user is playing in turn.  This is complicated because we
6413        let the user "pick up" a piece before it is his turn.  So the piece he
6414        tried to pick up may have been captured by the time he puts it down!
6415        Therefore we use the color the user is supposed to be playing in this
6416        test, not the color of the piece that is currently on the starting
6417        square---except in EditGame mode, where the user is playing both
6418        sides; fortunately there the capture race can't happen.  (It can
6419        now happen in IcsExamining mode, but that's just too bad.  The user
6420        will get a somewhat confusing message in that case.)
6421        */
6422
6423     switch (gameMode) {
6424       case AnalyzeFile:
6425       case TwoMachinesPlay:
6426       case EndOfGame:
6427       case IcsObserving:
6428       case IcsIdle:
6429         /* We switched into a game mode where moves are not accepted,
6430            perhaps while the mouse button was down. */
6431         return;
6432
6433       case MachinePlaysWhite:
6434         /* User is moving for Black */
6435         if (WhiteOnMove(currentMove)) {
6436             DisplayMoveError(_("It is White's turn"));
6437             return;
6438         }
6439         break;
6440
6441       case MachinePlaysBlack:
6442         /* User is moving for White */
6443         if (!WhiteOnMove(currentMove)) {
6444             DisplayMoveError(_("It is Black's turn"));
6445             return;
6446         }
6447         break;
6448
6449       case PlayFromGameFile:
6450             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6451       case EditGame:
6452       case IcsExamining:
6453       case BeginningOfGame:
6454       case AnalyzeMode:
6455       case Training:
6456         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6457         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6458             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6459             /* User is moving for Black */
6460             if (WhiteOnMove(currentMove)) {
6461                 DisplayMoveError(_("It is White's turn"));
6462                 return;
6463             }
6464         } else {
6465             /* User is moving for White */
6466             if (!WhiteOnMove(currentMove)) {
6467                 DisplayMoveError(_("It is Black's turn"));
6468                 return;
6469             }
6470         }
6471         break;
6472
6473       case IcsPlayingBlack:
6474         /* User is moving for Black */
6475         if (WhiteOnMove(currentMove)) {
6476             if (!appData.premove) {
6477                 DisplayMoveError(_("It is White's turn"));
6478             } else if (toX >= 0 && toY >= 0) {
6479                 premoveToX = toX;
6480                 premoveToY = toY;
6481                 premoveFromX = fromX;
6482                 premoveFromY = fromY;
6483                 premovePromoChar = promoChar;
6484                 gotPremove = 1;
6485                 if (appData.debugMode)
6486                     fprintf(debugFP, "Got premove: fromX %d,"
6487                             "fromY %d, toX %d, toY %d\n",
6488                             fromX, fromY, toX, toY);
6489             }
6490             return;
6491         }
6492         break;
6493
6494       case IcsPlayingWhite:
6495         /* User is moving for White */
6496         if (!WhiteOnMove(currentMove)) {
6497             if (!appData.premove) {
6498                 DisplayMoveError(_("It is Black's turn"));
6499             } else if (toX >= 0 && toY >= 0) {
6500                 premoveToX = toX;
6501                 premoveToY = toY;
6502                 premoveFromX = fromX;
6503                 premoveFromY = fromY;
6504                 premovePromoChar = promoChar;
6505                 gotPremove = 1;
6506                 if (appData.debugMode)
6507                     fprintf(debugFP, "Got premove: fromX %d,"
6508                             "fromY %d, toX %d, toY %d\n",
6509                             fromX, fromY, toX, toY);
6510             }
6511             return;
6512         }
6513         break;
6514
6515       default:
6516         break;
6517
6518       case EditPosition:
6519         /* EditPosition, empty square, or different color piece;
6520            click-click move is possible */
6521         if (toX == -2 || toY == -2) {
6522             boards[0][fromY][fromX] = EmptySquare;
6523             DrawPosition(FALSE, boards[currentMove]);
6524             return;
6525         } else if (toX >= 0 && toY >= 0) {
6526             boards[0][toY][toX] = boards[0][fromY][fromX];
6527             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6528                 if(boards[0][fromY][0] != EmptySquare) {
6529                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6530                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6531                 }
6532             } else
6533             if(fromX == BOARD_RGHT+1) {
6534                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6535                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6536                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6537                 }
6538             } else
6539             boards[0][fromY][fromX] = EmptySquare;
6540             DrawPosition(FALSE, boards[currentMove]);
6541             return;
6542         }
6543         return;
6544     }
6545
6546     if(toX < 0 || toY < 0) return;
6547     pdown = boards[currentMove][fromY][fromX];
6548     pup = boards[currentMove][toY][toX];
6549
6550     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6551     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6552          if( pup != EmptySquare ) return;
6553          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6554            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6555                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6556            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6557            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6558            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6559            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6560          fromY = DROP_RANK;
6561     }
6562
6563     /* [HGM] always test for legality, to get promotion info */
6564     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6565                                          fromY, fromX, toY, toX, promoChar);
6566
6567     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6568
6569     /* [HGM] but possibly ignore an IllegalMove result */
6570     if (appData.testLegality) {
6571         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6572             DisplayMoveError(_("Illegal move"));
6573             return;
6574         }
6575     }
6576
6577     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6578 }
6579
6580 /* Common tail of UserMoveEvent and DropMenuEvent */
6581 int
6582 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6583      ChessMove moveType;
6584      int fromX, fromY, toX, toY;
6585      /*char*/int promoChar;
6586 {
6587     char *bookHit = 0;
6588
6589     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6590         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6591         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6592         if(WhiteOnMove(currentMove)) {
6593             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6594         } else {
6595             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6596         }
6597     }
6598
6599     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6600        move type in caller when we know the move is a legal promotion */
6601     if(moveType == NormalMove && promoChar)
6602         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6603
6604     /* [HGM] <popupFix> The following if has been moved here from
6605        UserMoveEvent(). Because it seemed to belong here (why not allow
6606        piece drops in training games?), and because it can only be
6607        performed after it is known to what we promote. */
6608     if (gameMode == Training) {
6609       /* compare the move played on the board to the next move in the
6610        * game. If they match, display the move and the opponent's response.
6611        * If they don't match, display an error message.
6612        */
6613       int saveAnimate;
6614       Board testBoard;
6615       CopyBoard(testBoard, boards[currentMove]);
6616       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6617
6618       if (CompareBoards(testBoard, boards[currentMove+1])) {
6619         ForwardInner(currentMove+1);
6620
6621         /* Autoplay the opponent's response.
6622          * if appData.animate was TRUE when Training mode was entered,
6623          * the response will be animated.
6624          */
6625         saveAnimate = appData.animate;
6626         appData.animate = animateTraining;
6627         ForwardInner(currentMove+1);
6628         appData.animate = saveAnimate;
6629
6630         /* check for the end of the game */
6631         if (currentMove >= forwardMostMove) {
6632           gameMode = PlayFromGameFile;
6633           ModeHighlight();
6634           SetTrainingModeOff();
6635           DisplayInformation(_("End of game"));
6636         }
6637       } else {
6638         DisplayError(_("Incorrect move"), 0);
6639       }
6640       return 1;
6641     }
6642
6643   /* Ok, now we know that the move is good, so we can kill
6644      the previous line in Analysis Mode */
6645   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6646                                 && currentMove < forwardMostMove) {
6647     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6648     else forwardMostMove = currentMove;
6649   }
6650
6651   /* If we need the chess program but it's dead, restart it */
6652   ResurrectChessProgram();
6653
6654   /* A user move restarts a paused game*/
6655   if (pausing)
6656     PauseEvent();
6657
6658   thinkOutput[0] = NULLCHAR;
6659
6660   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6661
6662   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6663     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6664     return 1;
6665   }
6666
6667   if (gameMode == BeginningOfGame) {
6668     if (appData.noChessProgram) {
6669       gameMode = EditGame;
6670       SetGameInfo();
6671     } else {
6672       char buf[MSG_SIZ];
6673       gameMode = MachinePlaysBlack;
6674       StartClocks();
6675       SetGameInfo();
6676       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6677       DisplayTitle(buf);
6678       if (first.sendName) {
6679         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6680         SendToProgram(buf, &first);
6681       }
6682       StartClocks();
6683     }
6684     ModeHighlight();
6685   }
6686
6687   /* Relay move to ICS or chess engine */
6688   if (appData.icsActive) {
6689     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6690         gameMode == IcsExamining) {
6691       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6692         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6693         SendToICS("draw ");
6694         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6695       }
6696       // also send plain move, in case ICS does not understand atomic claims
6697       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6698       ics_user_moved = 1;
6699     }
6700   } else {
6701     if (first.sendTime && (gameMode == BeginningOfGame ||
6702                            gameMode == MachinePlaysWhite ||
6703                            gameMode == MachinePlaysBlack)) {
6704       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6705     }
6706     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6707          // [HGM] book: if program might be playing, let it use book
6708         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6709         first.maybeThinking = TRUE;
6710     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6711         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6712         SendBoard(&first, currentMove+1);
6713     } else SendMoveToProgram(forwardMostMove-1, &first);
6714     if (currentMove == cmailOldMove + 1) {
6715       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6716     }
6717   }
6718
6719   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6720
6721   switch (gameMode) {
6722   case EditGame:
6723     if(appData.testLegality)
6724     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6725     case MT_NONE:
6726     case MT_CHECK:
6727       break;
6728     case MT_CHECKMATE:
6729     case MT_STAINMATE:
6730       if (WhiteOnMove(currentMove)) {
6731         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6732       } else {
6733         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6734       }
6735       break;
6736     case MT_STALEMATE:
6737       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6738       break;
6739     }
6740     break;
6741
6742   case MachinePlaysBlack:
6743   case MachinePlaysWhite:
6744     /* disable certain menu options while machine is thinking */
6745     SetMachineThinkingEnables();
6746     break;
6747
6748   default:
6749     break;
6750   }
6751
6752   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6753   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6754
6755   if(bookHit) { // [HGM] book: simulate book reply
6756         static char bookMove[MSG_SIZ]; // a bit generous?
6757
6758         programStats.nodes = programStats.depth = programStats.time =
6759         programStats.score = programStats.got_only_move = 0;
6760         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6761
6762         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6763         strcat(bookMove, bookHit);
6764         HandleMachineMove(bookMove, &first);
6765   }
6766   return 1;
6767 }
6768
6769 void
6770 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6771      Board board;
6772      int flags;
6773      ChessMove kind;
6774      int rf, ff, rt, ft;
6775      VOIDSTAR closure;
6776 {
6777     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6778     Markers *m = (Markers *) closure;
6779     if(rf == fromY && ff == fromX)
6780         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6781                          || kind == WhiteCapturesEnPassant
6782                          || kind == BlackCapturesEnPassant);
6783     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6784 }
6785
6786 void
6787 MarkTargetSquares(int clear)
6788 {
6789   int x, y;
6790   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6791      !appData.testLegality || gameMode == EditPosition) return;
6792   if(clear) {
6793     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6794   } else {
6795     int capt = 0;
6796     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6797     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6798       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6799       if(capt)
6800       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6801     }
6802   }
6803   DrawPosition(TRUE, NULL);
6804 }
6805
6806 int
6807 Explode(Board board, int fromX, int fromY, int toX, int toY)
6808 {
6809     if(gameInfo.variant == VariantAtomic &&
6810        (board[toY][toX] != EmptySquare ||                     // capture?
6811         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6812                          board[fromY][fromX] == BlackPawn   )
6813       )) {
6814         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6815         return TRUE;
6816     }
6817     return FALSE;
6818 }
6819
6820 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6821
6822 int CanPromote(ChessSquare piece, int y)
6823 {
6824         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6825         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6826         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6827            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6828            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6829                                                   gameInfo.variant == VariantMakruk) return FALSE;
6830         return (piece == BlackPawn && y == 1 ||
6831                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6832                 piece == BlackLance && y == 1 ||
6833                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6834 }
6835
6836 void LeftClick(ClickType clickType, int xPix, int yPix)
6837 {
6838     int x, y;
6839     Boolean saveAnimate;
6840     static int second = 0, promotionChoice = 0, clearFlag = 0;
6841     char promoChoice = NULLCHAR;
6842     ChessSquare piece;
6843
6844     if(appData.seekGraph && appData.icsActive && loggedOn &&
6845         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6846         SeekGraphClick(clickType, xPix, yPix, 0);
6847         return;
6848     }
6849
6850     if (clickType == Press) ErrorPopDown();
6851
6852     x = EventToSquare(xPix, BOARD_WIDTH);
6853     y = EventToSquare(yPix, BOARD_HEIGHT);
6854     if (!flipView && y >= 0) {
6855         y = BOARD_HEIGHT - 1 - y;
6856     }
6857     if (flipView && x >= 0) {
6858         x = BOARD_WIDTH - 1 - x;
6859     }
6860
6861     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6862         defaultPromoChoice = promoSweep;
6863         promoSweep = EmptySquare;   // terminate sweep
6864         promoDefaultAltered = TRUE;
6865         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6866     }
6867
6868     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6869         if(clickType == Release) return; // ignore upclick of click-click destination
6870         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6871         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6872         if(gameInfo.holdingsWidth &&
6873                 (WhiteOnMove(currentMove)
6874                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6875                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6876             // click in right holdings, for determining promotion piece
6877             ChessSquare p = boards[currentMove][y][x];
6878             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6879             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6880             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6881                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6882                 fromX = fromY = -1;
6883                 return;
6884             }
6885         }
6886         DrawPosition(FALSE, boards[currentMove]);
6887         return;
6888     }
6889
6890     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6891     if(clickType == Press
6892             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6893               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6894               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6895         return;
6896
6897     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6898         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6899
6900     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6901         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6902                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6903         defaultPromoChoice = DefaultPromoChoice(side);
6904     }
6905
6906     autoQueen = appData.alwaysPromoteToQueen;
6907
6908     if (fromX == -1) {
6909       int originalY = y;
6910       gatingPiece = EmptySquare;
6911       if (clickType != Press) {
6912         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6913             DragPieceEnd(xPix, yPix); dragging = 0;
6914             DrawPosition(FALSE, NULL);
6915         }
6916         return;
6917       }
6918       fromX = x; fromY = y; toX = toY = -1;
6919       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6920          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6921          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6922             /* First square */
6923             if (OKToStartUserMove(fromX, fromY)) {
6924                 second = 0;
6925                 MarkTargetSquares(0);
6926                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6927                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6928                     promoSweep = defaultPromoChoice;
6929                     selectFlag = 0; lastX = xPix; lastY = yPix;
6930                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6931                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6932                 }
6933                 if (appData.highlightDragging) {
6934                     SetHighlights(fromX, fromY, -1, -1);
6935                 }
6936             } else fromX = fromY = -1;
6937             return;
6938         }
6939     }
6940
6941     /* fromX != -1 */
6942     if (clickType == Press && gameMode != EditPosition) {
6943         ChessSquare fromP;
6944         ChessSquare toP;
6945         int frc;
6946
6947         // ignore off-board to clicks
6948         if(y < 0 || x < 0) return;
6949
6950         /* Check if clicking again on the same color piece */
6951         fromP = boards[currentMove][fromY][fromX];
6952         toP = boards[currentMove][y][x];
6953         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6954         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6955              WhitePawn <= toP && toP <= WhiteKing &&
6956              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6957              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6958             (BlackPawn <= fromP && fromP <= BlackKing &&
6959              BlackPawn <= toP && toP <= BlackKing &&
6960              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6961              !(fromP == BlackKing && toP == BlackRook && frc))) {
6962             /* Clicked again on same color piece -- changed his mind */
6963             second = (x == fromX && y == fromY);
6964             promoDefaultAltered = FALSE;
6965             MarkTargetSquares(1);
6966            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6967             if (appData.highlightDragging) {
6968                 SetHighlights(x, y, -1, -1);
6969             } else {
6970                 ClearHighlights();
6971             }
6972             if (OKToStartUserMove(x, y)) {
6973                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6974                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6975                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6976                  gatingPiece = boards[currentMove][fromY][fromX];
6977                 else gatingPiece = EmptySquare;
6978                 fromX = x;
6979                 fromY = y; dragging = 1;
6980                 MarkTargetSquares(0);
6981                 DragPieceBegin(xPix, yPix, FALSE);
6982                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6983                     promoSweep = defaultPromoChoice;
6984                     selectFlag = 0; lastX = xPix; lastY = yPix;
6985                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6986                 }
6987             }
6988            }
6989            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6990            second = FALSE; 
6991         }
6992         // ignore clicks on holdings
6993         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6994     }
6995
6996     if (clickType == Release && x == fromX && y == fromY) {
6997         DragPieceEnd(xPix, yPix); dragging = 0;
6998         if(clearFlag) {
6999             // a deferred attempt to click-click move an empty square on top of a piece
7000             boards[currentMove][y][x] = EmptySquare;
7001             ClearHighlights();
7002             DrawPosition(FALSE, boards[currentMove]);
7003             fromX = fromY = -1; clearFlag = 0;
7004             return;
7005         }
7006         if (appData.animateDragging) {
7007             /* Undo animation damage if any */
7008             DrawPosition(FALSE, NULL);
7009         }
7010         if (second) {
7011             /* Second up/down in same square; just abort move */
7012             second = 0;
7013             fromX = fromY = -1;
7014             gatingPiece = EmptySquare;
7015             ClearHighlights();
7016             gotPremove = 0;
7017             ClearPremoveHighlights();
7018         } else {
7019             /* First upclick in same square; start click-click mode */
7020             SetHighlights(x, y, -1, -1);
7021         }
7022         return;
7023     }
7024
7025     clearFlag = 0;
7026
7027     /* we now have a different from- and (possibly off-board) to-square */
7028     /* Completed move */
7029     toX = x;
7030     toY = y;
7031     saveAnimate = appData.animate;
7032     MarkTargetSquares(1);
7033     if (clickType == Press) {
7034         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7035             // must be Edit Position mode with empty-square selected
7036             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7037             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7038             return;
7039         }
7040         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7041             ChessSquare piece = boards[currentMove][fromY][fromX];
7042             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7043             promoSweep = defaultPromoChoice;
7044             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7045             selectFlag = 0; lastX = xPix; lastY = yPix;
7046             Sweep(0); // Pawn that is going to promote: preview promotion piece
7047             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7048             DrawPosition(FALSE, boards[currentMove]);
7049             return;
7050         }
7051         /* Finish clickclick move */
7052         if (appData.animate || appData.highlightLastMove) {
7053             SetHighlights(fromX, fromY, toX, toY);
7054         } else {
7055             ClearHighlights();
7056         }
7057     } else {
7058         /* Finish drag move */
7059         if (appData.highlightLastMove) {
7060             SetHighlights(fromX, fromY, toX, toY);
7061         } else {
7062             ClearHighlights();
7063         }
7064         DragPieceEnd(xPix, yPix); dragging = 0;
7065         /* Don't animate move and drag both */
7066         appData.animate = FALSE;
7067     }
7068
7069     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7070     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7071         ChessSquare piece = boards[currentMove][fromY][fromX];
7072         if(gameMode == EditPosition && piece != EmptySquare &&
7073            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7074             int n;
7075
7076             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7077                 n = PieceToNumber(piece - (int)BlackPawn);
7078                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7079                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7080                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7081             } else
7082             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7083                 n = PieceToNumber(piece);
7084                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7085                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7086                 boards[currentMove][n][BOARD_WIDTH-2]++;
7087             }
7088             boards[currentMove][fromY][fromX] = EmptySquare;
7089         }
7090         ClearHighlights();
7091         fromX = fromY = -1;
7092         DrawPosition(TRUE, boards[currentMove]);
7093         return;
7094     }
7095
7096     // off-board moves should not be highlighted
7097     if(x < 0 || y < 0) ClearHighlights();
7098
7099     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7100
7101     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7102         SetHighlights(fromX, fromY, toX, toY);
7103         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7104             // [HGM] super: promotion to captured piece selected from holdings
7105             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7106             promotionChoice = TRUE;
7107             // kludge follows to temporarily execute move on display, without promoting yet
7108             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7109             boards[currentMove][toY][toX] = p;
7110             DrawPosition(FALSE, boards[currentMove]);
7111             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7112             boards[currentMove][toY][toX] = q;
7113             DisplayMessage("Click in holdings to choose piece", "");
7114             return;
7115         }
7116         PromotionPopUp();
7117     } else {
7118         int oldMove = currentMove;
7119         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7120         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7121         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7122         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7123            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7124             DrawPosition(TRUE, boards[currentMove]);
7125         fromX = fromY = -1;
7126     }
7127     appData.animate = saveAnimate;
7128     if (appData.animate || appData.animateDragging) {
7129         /* Undo animation damage if needed */
7130         DrawPosition(FALSE, NULL);
7131     }
7132 }
7133
7134 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7135 {   // front-end-free part taken out of PieceMenuPopup
7136     int whichMenu; int xSqr, ySqr;
7137
7138     if(seekGraphUp) { // [HGM] seekgraph
7139         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7140         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7141         return -2;
7142     }
7143
7144     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7145          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7146         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7147         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7148         if(action == Press)   {
7149             originalFlip = flipView;
7150             flipView = !flipView; // temporarily flip board to see game from partners perspective
7151             DrawPosition(TRUE, partnerBoard);
7152             DisplayMessage(partnerStatus, "");
7153             partnerUp = TRUE;
7154         } else if(action == Release) {
7155             flipView = originalFlip;
7156             DrawPosition(TRUE, boards[currentMove]);
7157             partnerUp = FALSE;
7158         }
7159         return -2;
7160     }
7161
7162     xSqr = EventToSquare(x, BOARD_WIDTH);
7163     ySqr = EventToSquare(y, BOARD_HEIGHT);
7164     if (action == Release) {
7165         if(pieceSweep != EmptySquare) {
7166             EditPositionMenuEvent(pieceSweep, toX, toY);
7167             pieceSweep = EmptySquare;
7168         } else UnLoadPV(); // [HGM] pv
7169     }
7170     if (action != Press) return -2; // return code to be ignored
7171     switch (gameMode) {
7172       case IcsExamining:
7173         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7174       case EditPosition:
7175         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7176         if (xSqr < 0 || ySqr < 0) return -1;
7177         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7178         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7179         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7180         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7181         NextPiece(0);
7182         return 2; // grab
7183       case IcsObserving:
7184         if(!appData.icsEngineAnalyze) return -1;
7185       case IcsPlayingWhite:
7186       case IcsPlayingBlack:
7187         if(!appData.zippyPlay) goto noZip;
7188       case AnalyzeMode:
7189       case AnalyzeFile:
7190       case MachinePlaysWhite:
7191       case MachinePlaysBlack:
7192       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7193         if (!appData.dropMenu) {
7194           LoadPV(x, y);
7195           return 2; // flag front-end to grab mouse events
7196         }
7197         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7198            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7199       case EditGame:
7200       noZip:
7201         if (xSqr < 0 || ySqr < 0) return -1;
7202         if (!appData.dropMenu || appData.testLegality &&
7203             gameInfo.variant != VariantBughouse &&
7204             gameInfo.variant != VariantCrazyhouse) return -1;
7205         whichMenu = 1; // drop menu
7206         break;
7207       default:
7208         return -1;
7209     }
7210
7211     if (((*fromX = xSqr) < 0) ||
7212         ((*fromY = ySqr) < 0)) {
7213         *fromX = *fromY = -1;
7214         return -1;
7215     }
7216     if (flipView)
7217       *fromX = BOARD_WIDTH - 1 - *fromX;
7218     else
7219       *fromY = BOARD_HEIGHT - 1 - *fromY;
7220
7221     return whichMenu;
7222 }
7223
7224 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7225 {
7226 //    char * hint = lastHint;
7227     FrontEndProgramStats stats;
7228
7229     stats.which = cps == &first ? 0 : 1;
7230     stats.depth = cpstats->depth;
7231     stats.nodes = cpstats->nodes;
7232     stats.score = cpstats->score;
7233     stats.time = cpstats->time;
7234     stats.pv = cpstats->movelist;
7235     stats.hint = lastHint;
7236     stats.an_move_index = 0;
7237     stats.an_move_count = 0;
7238
7239     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7240         stats.hint = cpstats->move_name;
7241         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7242         stats.an_move_count = cpstats->nr_moves;
7243     }
7244
7245     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
7246
7247     SetProgramStats( &stats );
7248 }
7249
7250 void
7251 ClearEngineOutputPane(int which)
7252 {
7253     static FrontEndProgramStats dummyStats;
7254     dummyStats.which = which;
7255     dummyStats.pv = "#";
7256     SetProgramStats( &dummyStats );
7257 }
7258
7259 #define MAXPLAYERS 500
7260
7261 char *
7262 TourneyStandings(int display)
7263 {
7264     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7265     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7266     char result, *p, *names[MAXPLAYERS];
7267
7268     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7269         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7270     names[0] = p = strdup(appData.participants);
7271     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7272
7273     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7274
7275     while(result = appData.results[nr]) {
7276         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7277         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7278         wScore = bScore = 0;
7279         switch(result) {
7280           case '+': wScore = 2; break;
7281           case '-': bScore = 2; break;
7282           case '=': wScore = bScore = 1; break;
7283           case ' ':
7284           case '*': return strdup("busy"); // tourney not finished
7285         }
7286         score[w] += wScore;
7287         score[b] += bScore;
7288         games[w]++;
7289         games[b]++;
7290         nr++;
7291     }
7292     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7293     for(w=0; w<nPlayers; w++) {
7294         bScore = -1;
7295         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7296         ranking[w] = b; points[w] = bScore; score[b] = -2;
7297     }
7298     p = malloc(nPlayers*34+1);
7299     for(w=0; w<nPlayers && w<display; w++)
7300         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7301     free(names[0]);
7302     return p;
7303 }
7304
7305 void
7306 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7307 {       // count all piece types
7308         int p, f, r;
7309         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7310         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7311         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7312                 p = board[r][f];
7313                 pCnt[p]++;
7314                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7315                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7316                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7317                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7318                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7319                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7320         }
7321 }
7322
7323 int
7324 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7325 {
7326         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7327         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7328
7329         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7330         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7331         if(myPawns == 2 && nMine == 3) // KPP
7332             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7333         if(myPawns == 1 && nMine == 2) // KP
7334             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7335         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7336             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7337         if(myPawns) return FALSE;
7338         if(pCnt[WhiteRook+side])
7339             return pCnt[BlackRook-side] ||
7340                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7341                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7342                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7343         if(pCnt[WhiteCannon+side]) {
7344             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7345             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7346         }
7347         if(pCnt[WhiteKnight+side])
7348             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7349         return FALSE;
7350 }
7351
7352 int
7353 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7354 {
7355         VariantClass v = gameInfo.variant;
7356
7357         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7358         if(v == VariantShatranj) return TRUE; // always winnable through baring
7359         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7360         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7361
7362         if(v == VariantXiangqi) {
7363                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7364
7365                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7366                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7367                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7368                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7369                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7370                 if(stale) // we have at least one last-rank P plus perhaps C
7371                     return majors // KPKX
7372                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7373                 else // KCA*E*
7374                     return pCnt[WhiteFerz+side] // KCAK
7375                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7376                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7377                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7378
7379         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7380                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7381
7382                 if(nMine == 1) return FALSE; // bare King
7383                 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
7384                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7385                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7386                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7387                 if(pCnt[WhiteKnight+side])
7388                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7389                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7390                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7391                 if(nBishops)
7392                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7393                 if(pCnt[WhiteAlfil+side])
7394                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7395                 if(pCnt[WhiteWazir+side])
7396                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7397         }
7398
7399         return TRUE;
7400 }
7401
7402 int
7403 CompareWithRights(Board b1, Board b2)
7404 {
7405     int rights = 0;
7406     if(!CompareBoards(b1, b2)) return FALSE;
7407     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7408     /* compare castling rights */
7409     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7410            rights++; /* King lost rights, while rook still had them */
7411     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7412         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7413            rights++; /* but at least one rook lost them */
7414     }
7415     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7416            rights++;
7417     if( b1[CASTLING][5] != NoRights ) {
7418         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7419            rights++;
7420     }
7421     return rights == 0;
7422 }
7423
7424 int
7425 Adjudicate(ChessProgramState *cps)
7426 {       // [HGM] some adjudications useful with buggy engines
7427         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7428         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7429         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7430         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7431         int k, count = 0; static int bare = 1;
7432         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7433         Boolean canAdjudicate = !appData.icsActive;
7434
7435         // most tests only when we understand the game, i.e. legality-checking on
7436             if( appData.testLegality )
7437             {   /* [HGM] Some more adjudications for obstinate engines */
7438                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7439                 static int moveCount = 6;
7440                 ChessMove result;
7441                 char *reason = NULL;
7442
7443                 /* Count what is on board. */
7444                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7445
7446                 /* Some material-based adjudications that have to be made before stalemate test */
7447                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7448                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7449                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7450                      if(canAdjudicate && appData.checkMates) {
7451                          if(engineOpponent)
7452                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7453                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7454                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7455                          return 1;
7456                      }
7457                 }
7458
7459                 /* Bare King in Shatranj (loses) or Losers (wins) */
7460                 if( nrW == 1 || nrB == 1) {
7461                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7462                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7463                      if(canAdjudicate && appData.checkMates) {
7464                          if(engineOpponent)
7465                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7466                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7467                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7468                          return 1;
7469                      }
7470                   } else
7471                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7472                   {    /* bare King */
7473                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7474                         if(canAdjudicate && appData.checkMates) {
7475                             /* but only adjudicate if adjudication enabled */
7476                             if(engineOpponent)
7477                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7478                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7479                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7480                             return 1;
7481                         }
7482                   }
7483                 } else bare = 1;
7484
7485
7486             // don't wait for engine to announce game end if we can judge ourselves
7487             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7488               case MT_CHECK:
7489                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7490                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7491                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7492                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7493                             checkCnt++;
7494                         if(checkCnt >= 2) {
7495                             reason = "Xboard adjudication: 3rd check";
7496                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7497                             break;
7498                         }
7499                     }
7500                 }
7501               case MT_NONE:
7502               default:
7503                 break;
7504               case MT_STALEMATE:
7505               case MT_STAINMATE:
7506                 reason = "Xboard adjudication: Stalemate";
7507                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7508                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7509                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7510                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7511                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7512                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7513                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7514                                                                         EP_CHECKMATE : EP_WINS);
7515                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7516                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7517                 }
7518                 break;
7519               case MT_CHECKMATE:
7520                 reason = "Xboard adjudication: Checkmate";
7521                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7522                 break;
7523             }
7524
7525                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7526                     case EP_STALEMATE:
7527                         result = GameIsDrawn; break;
7528                     case EP_CHECKMATE:
7529                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7530                     case EP_WINS:
7531                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7532                     default:
7533                         result = EndOfFile;
7534                 }
7535                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7536                     if(engineOpponent)
7537                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7538                     GameEnds( result, reason, GE_XBOARD );
7539                     return 1;
7540                 }
7541
7542                 /* Next absolutely insufficient mating material. */
7543                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7544                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7545                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7546
7547                      /* always flag draws, for judging claims */
7548                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7549
7550                      if(canAdjudicate && appData.materialDraws) {
7551                          /* but only adjudicate them if adjudication enabled */
7552                          if(engineOpponent) {
7553                            SendToProgram("force\n", engineOpponent); // suppress reply
7554                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7555                          }
7556                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7557                          return 1;
7558                      }
7559                 }
7560
7561                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7562                 if(gameInfo.variant == VariantXiangqi ?
7563                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7564                  : nrW + nrB == 4 &&
7565                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7566                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7567                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7568                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7569                    ) ) {
7570                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7571                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7572                           if(engineOpponent) {
7573                             SendToProgram("force\n", engineOpponent); // suppress reply
7574                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7575                           }
7576                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7577                           return 1;
7578                      }
7579                 } else moveCount = 6;
7580             }
7581         if (appData.debugMode) { int i;
7582             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7583                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7584                     appData.drawRepeats);
7585             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7586               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7587
7588         }
7589
7590         // Repetition draws and 50-move rule can be applied independently of legality testing
7591
7592                 /* Check for rep-draws */
7593                 count = 0;
7594                 for(k = forwardMostMove-2;
7595                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7596                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7597                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7598                     k-=2)
7599                 {   int rights=0;
7600                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7601                         /* compare castling rights */
7602                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7603                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7604                                 rights++; /* King lost rights, while rook still had them */
7605                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7606                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7607                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7608                                    rights++; /* but at least one rook lost them */
7609                         }
7610                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7611                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7612                                 rights++;
7613                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7614                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7615                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7616                                    rights++;
7617                         }
7618                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7619                             && appData.drawRepeats > 1) {
7620                              /* adjudicate after user-specified nr of repeats */
7621                              int result = GameIsDrawn;
7622                              char *details = "XBoard adjudication: repetition draw";
7623                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7624                                 // [HGM] xiangqi: check for forbidden perpetuals
7625                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7626                                 for(m=forwardMostMove; m>k; m-=2) {
7627                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7628                                         ourPerpetual = 0; // the current mover did not always check
7629                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7630                                         hisPerpetual = 0; // the opponent did not always check
7631                                 }
7632                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7633                                                                         ourPerpetual, hisPerpetual);
7634                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7635                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7636                                     details = "Xboard adjudication: perpetual checking";
7637                                 } else
7638                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7639                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7640                                 } else
7641                                 // Now check for perpetual chases
7642                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7643                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7644                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7645                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7646                                         static char resdet[MSG_SIZ];
7647                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7648                                         details = resdet;
7649                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7650                                     } else
7651                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7652                                         break; // Abort repetition-checking loop.
7653                                 }
7654                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7655                              }
7656                              if(engineOpponent) {
7657                                SendToProgram("force\n", engineOpponent); // suppress reply
7658                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7659                              }
7660                              GameEnds( result, details, GE_XBOARD );
7661                              return 1;
7662                         }
7663                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7664                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7665                     }
7666                 }
7667
7668                 /* Now we test for 50-move draws. Determine ply count */
7669                 count = forwardMostMove;
7670                 /* look for last irreversble move */
7671                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7672                     count--;
7673                 /* if we hit starting position, add initial plies */
7674                 if( count == backwardMostMove )
7675                     count -= initialRulePlies;
7676                 count = forwardMostMove - count;
7677                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7678                         // adjust reversible move counter for checks in Xiangqi
7679                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7680                         if(i < backwardMostMove) i = backwardMostMove;
7681                         while(i <= forwardMostMove) {
7682                                 lastCheck = inCheck; // check evasion does not count
7683                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7684                                 if(inCheck || lastCheck) count--; // check does not count
7685                                 i++;
7686                         }
7687                 }
7688                 if( count >= 100)
7689                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7690                          /* this is used to judge if draw claims are legal */
7691                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7692                          if(engineOpponent) {
7693                            SendToProgram("force\n", engineOpponent); // suppress reply
7694                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7695                          }
7696                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7697                          return 1;
7698                 }
7699
7700                 /* if draw offer is pending, treat it as a draw claim
7701                  * when draw condition present, to allow engines a way to
7702                  * claim draws before making their move to avoid a race
7703                  * condition occurring after their move
7704                  */
7705                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7706                          char *p = NULL;
7707                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7708                              p = "Draw claim: 50-move rule";
7709                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7710                              p = "Draw claim: 3-fold repetition";
7711                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7712                              p = "Draw claim: insufficient mating material";
7713                          if( p != NULL && canAdjudicate) {
7714                              if(engineOpponent) {
7715                                SendToProgram("force\n", engineOpponent); // suppress reply
7716                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7717                              }
7718                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7719                              return 1;
7720                          }
7721                 }
7722
7723                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7724                     if(engineOpponent) {
7725                       SendToProgram("force\n", engineOpponent); // suppress reply
7726                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7727                     }
7728                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7729                     return 1;
7730                 }
7731         return 0;
7732 }
7733
7734 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7735 {   // [HGM] book: this routine intercepts moves to simulate book replies
7736     char *bookHit = NULL;
7737
7738     //first determine if the incoming move brings opponent into his book
7739     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7740         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7741     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7742     if(bookHit != NULL && !cps->bookSuspend) {
7743         // make sure opponent is not going to reply after receiving move to book position
7744         SendToProgram("force\n", cps);
7745         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7746     }
7747     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7748     // now arrange restart after book miss
7749     if(bookHit) {
7750         // after a book hit we never send 'go', and the code after the call to this routine
7751         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7752         char buf[MSG_SIZ], *move = bookHit;
7753         if(cps->useSAN) {
7754             int fromX, fromY, toX, toY;
7755             char promoChar;
7756             ChessMove moveType;
7757             move = buf + 30;
7758             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7759                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7760                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7761                                     PosFlags(forwardMostMove),
7762                                     fromY, fromX, toY, toX, promoChar, move);
7763             } else {
7764                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7765                 bookHit = NULL;
7766             }
7767         }
7768         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7769         SendToProgram(buf, cps);
7770         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7771     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7772         SendToProgram("go\n", cps);
7773         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7774     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7775         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7776             SendToProgram("go\n", cps);
7777         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7778     }
7779     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7780 }
7781
7782 char *savedMessage;
7783 ChessProgramState *savedState;
7784 void DeferredBookMove(void)
7785 {
7786         if(savedState->lastPing != savedState->lastPong)
7787                     ScheduleDelayedEvent(DeferredBookMove, 10);
7788         else
7789         HandleMachineMove(savedMessage, savedState);
7790 }
7791
7792 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7793
7794 void
7795 HandleMachineMove(message, cps)
7796      char *message;
7797      ChessProgramState *cps;
7798 {
7799     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7800     char realname[MSG_SIZ];
7801     int fromX, fromY, toX, toY;
7802     ChessMove moveType;
7803     char promoChar;
7804     char *p, *pv=buf1;
7805     int machineWhite;
7806     char *bookHit;
7807
7808     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7809         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7810         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7811             DisplayError(_("Invalid pairing from pairing engine"), 0);
7812             return;
7813         }
7814         pairingReceived = 1;
7815         NextMatchGame();
7816         return; // Skim the pairing messages here.
7817     }
7818
7819     cps->userError = 0;
7820
7821 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7822     /*
7823      * Kludge to ignore BEL characters
7824      */
7825     while (*message == '\007') message++;
7826
7827     /*
7828      * [HGM] engine debug message: ignore lines starting with '#' character
7829      */
7830     if(cps->debug && *message == '#') return;
7831
7832     /*
7833      * Look for book output
7834      */
7835     if (cps == &first && bookRequested) {
7836         if (message[0] == '\t' || message[0] == ' ') {
7837             /* Part of the book output is here; append it */
7838             strcat(bookOutput, message);
7839             strcat(bookOutput, "  \n");
7840             return;
7841         } else if (bookOutput[0] != NULLCHAR) {
7842             /* All of book output has arrived; display it */
7843             char *p = bookOutput;
7844             while (*p != NULLCHAR) {
7845                 if (*p == '\t') *p = ' ';
7846                 p++;
7847             }
7848             DisplayInformation(bookOutput);
7849             bookRequested = FALSE;
7850             /* Fall through to parse the current output */
7851         }
7852     }
7853
7854     /*
7855      * Look for machine move.
7856      */
7857     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7858         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7859     {
7860         /* This method is only useful on engines that support ping */
7861         if (cps->lastPing != cps->lastPong) {
7862           if (gameMode == BeginningOfGame) {
7863             /* Extra move from before last new; ignore */
7864             if (appData.debugMode) {
7865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7866             }
7867           } else {
7868             if (appData.debugMode) {
7869                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7870                         cps->which, gameMode);
7871             }
7872
7873             SendToProgram("undo\n", cps);
7874           }
7875           return;
7876         }
7877
7878         switch (gameMode) {
7879           case BeginningOfGame:
7880             /* Extra move from before last reset; ignore */
7881             if (appData.debugMode) {
7882                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7883             }
7884             return;
7885
7886           case EndOfGame:
7887           case IcsIdle:
7888           default:
7889             /* Extra move after we tried to stop.  The mode test is
7890                not a reliable way of detecting this problem, but it's
7891                the best we can do on engines that don't support ping.
7892             */
7893             if (appData.debugMode) {
7894                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7895                         cps->which, gameMode);
7896             }
7897             SendToProgram("undo\n", cps);
7898             return;
7899
7900           case MachinePlaysWhite:
7901           case IcsPlayingWhite:
7902             machineWhite = TRUE;
7903             break;
7904
7905           case MachinePlaysBlack:
7906           case IcsPlayingBlack:
7907             machineWhite = FALSE;
7908             break;
7909
7910           case TwoMachinesPlay:
7911             machineWhite = (cps->twoMachinesColor[0] == 'w');
7912             break;
7913         }
7914         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7915             if (appData.debugMode) {
7916                 fprintf(debugFP,
7917                         "Ignoring move out of turn by %s, gameMode %d"
7918                         ", forwardMost %d\n",
7919                         cps->which, gameMode, forwardMostMove);
7920             }
7921             return;
7922         }
7923
7924     if (appData.debugMode) { int f = forwardMostMove;
7925         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7926                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7927                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7928     }
7929         if(cps->alphaRank) AlphaRank(machineMove, 4);
7930         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7931                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7932             /* Machine move could not be parsed; ignore it. */
7933           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7934                     machineMove, _(cps->which));
7935             DisplayError(buf1, 0);
7936             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7937                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7938             if (gameMode == TwoMachinesPlay) {
7939               GameEnds(machineWhite ? BlackWins : WhiteWins,
7940                        buf1, GE_XBOARD);
7941             }
7942             return;
7943         }
7944
7945         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7946         /* So we have to redo legality test with true e.p. status here,  */
7947         /* to make sure an illegal e.p. capture does not slip through,   */
7948         /* to cause a forfeit on a justified illegal-move complaint      */
7949         /* of the opponent.                                              */
7950         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7951            ChessMove moveType;
7952            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7953                              fromY, fromX, toY, toX, promoChar);
7954             if (appData.debugMode) {
7955                 int i;
7956                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7957                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7958                 fprintf(debugFP, "castling rights\n");
7959             }
7960             if(moveType == IllegalMove) {
7961               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7962                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7963                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7964                            buf1, GE_XBOARD);
7965                 return;
7966            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7967            /* [HGM] Kludge to handle engines that send FRC-style castling
7968               when they shouldn't (like TSCP-Gothic) */
7969            switch(moveType) {
7970              case WhiteASideCastleFR:
7971              case BlackASideCastleFR:
7972                toX+=2;
7973                currentMoveString[2]++;
7974                break;
7975              case WhiteHSideCastleFR:
7976              case BlackHSideCastleFR:
7977                toX--;
7978                currentMoveString[2]--;
7979                break;
7980              default: ; // nothing to do, but suppresses warning of pedantic compilers
7981            }
7982         }
7983         hintRequested = FALSE;
7984         lastHint[0] = NULLCHAR;
7985         bookRequested = FALSE;
7986         /* Program may be pondering now */
7987         cps->maybeThinking = TRUE;
7988         if (cps->sendTime == 2) cps->sendTime = 1;
7989         if (cps->offeredDraw) cps->offeredDraw--;
7990
7991         /* [AS] Save move info*/
7992         pvInfoList[ forwardMostMove ].score = programStats.score;
7993         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7994         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7995
7996         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7997
7998         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7999         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8000             int count = 0;
8001
8002             while( count < adjudicateLossPlies ) {
8003                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8004
8005                 if( count & 1 ) {
8006                     score = -score; /* Flip score for winning side */
8007                 }
8008
8009                 if( score > adjudicateLossThreshold ) {
8010                     break;
8011                 }
8012
8013                 count++;
8014             }
8015
8016             if( count >= adjudicateLossPlies ) {
8017                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8018
8019                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8020                     "Xboard adjudication",
8021                     GE_XBOARD );
8022
8023                 return;
8024             }
8025         }
8026
8027         if(Adjudicate(cps)) {
8028             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8029             return; // [HGM] adjudicate: for all automatic game ends
8030         }
8031
8032 #if ZIPPY
8033         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8034             first.initDone) {
8035           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8036                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8037                 SendToICS("draw ");
8038                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8039           }
8040           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8041           ics_user_moved = 1;
8042           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8043                 char buf[3*MSG_SIZ];
8044
8045                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8046                         programStats.score / 100.,
8047                         programStats.depth,
8048                         programStats.time / 100.,
8049                         (unsigned int)programStats.nodes,
8050                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8051                         programStats.movelist);
8052                 SendToICS(buf);
8053 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8054           }
8055         }
8056 #endif
8057
8058         /* [AS] Clear stats for next move */
8059         ClearProgramStats();
8060         thinkOutput[0] = NULLCHAR;
8061         hiddenThinkOutputState = 0;
8062
8063         bookHit = NULL;
8064         if (gameMode == TwoMachinesPlay) {
8065             /* [HGM] relaying draw offers moved to after reception of move */
8066             /* and interpreting offer as claim if it brings draw condition */
8067             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8068                 SendToProgram("draw\n", cps->other);
8069             }
8070             if (cps->other->sendTime) {
8071                 SendTimeRemaining(cps->other,
8072                                   cps->other->twoMachinesColor[0] == 'w');
8073             }
8074             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8075             if (firstMove && !bookHit) {
8076                 firstMove = FALSE;
8077                 if (cps->other->useColors) {
8078                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8079                 }
8080                 SendToProgram("go\n", cps->other);
8081             }
8082             cps->other->maybeThinking = TRUE;
8083         }
8084
8085         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8086
8087         if (!pausing && appData.ringBellAfterMoves) {
8088             RingBell();
8089         }
8090
8091         /*
8092          * Reenable menu items that were disabled while
8093          * machine was thinking
8094          */
8095         if (gameMode != TwoMachinesPlay)
8096             SetUserThinkingEnables();
8097
8098         // [HGM] book: after book hit opponent has received move and is now in force mode
8099         // force the book reply into it, and then fake that it outputted this move by jumping
8100         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8101         if(bookHit) {
8102                 static char bookMove[MSG_SIZ]; // a bit generous?
8103
8104                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8105                 strcat(bookMove, bookHit);
8106                 message = bookMove;
8107                 cps = cps->other;
8108                 programStats.nodes = programStats.depth = programStats.time =
8109                 programStats.score = programStats.got_only_move = 0;
8110                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8111
8112                 if(cps->lastPing != cps->lastPong) {
8113                     savedMessage = message; // args for deferred call
8114                     savedState = cps;
8115                     ScheduleDelayedEvent(DeferredBookMove, 10);
8116                     return;
8117                 }
8118                 goto FakeBookMove;
8119         }
8120
8121         return;
8122     }
8123
8124     /* Set special modes for chess engines.  Later something general
8125      *  could be added here; for now there is just one kludge feature,
8126      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8127      *  when "xboard" is given as an interactive command.
8128      */
8129     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8130         cps->useSigint = FALSE;
8131         cps->useSigterm = FALSE;
8132     }
8133     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8134       ParseFeatures(message+8, cps);
8135       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8136     }
8137
8138     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8139                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8140       int dummy, s=6; char buf[MSG_SIZ];
8141       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8142       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8143       if(startedFromSetupPosition) return;
8144       ParseFEN(boards[0], &dummy, message+s);
8145       DrawPosition(TRUE, boards[0]);
8146       startedFromSetupPosition = TRUE;
8147       return;
8148     }
8149     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8150      * want this, I was asked to put it in, and obliged.
8151      */
8152     if (!strncmp(message, "setboard ", 9)) {
8153         Board initial_position;
8154
8155         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8156
8157         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8158             DisplayError(_("Bad FEN received from engine"), 0);
8159             return ;
8160         } else {
8161            Reset(TRUE, FALSE);
8162            CopyBoard(boards[0], initial_position);
8163            initialRulePlies = FENrulePlies;
8164            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8165            else gameMode = MachinePlaysBlack;
8166            DrawPosition(FALSE, boards[currentMove]);
8167         }
8168         return;
8169     }
8170
8171     /*
8172      * Look for communication commands
8173      */
8174     if (!strncmp(message, "telluser ", 9)) {
8175         if(message[9] == '\\' && message[10] == '\\')
8176             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8177         PlayTellSound();
8178         DisplayNote(message + 9);
8179         return;
8180     }
8181     if (!strncmp(message, "tellusererror ", 14)) {
8182         cps->userError = 1;
8183         if(message[14] == '\\' && message[15] == '\\')
8184             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8185         PlayTellSound();
8186         DisplayError(message + 14, 0);
8187         return;
8188     }
8189     if (!strncmp(message, "tellopponent ", 13)) {
8190       if (appData.icsActive) {
8191         if (loggedOn) {
8192           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8193           SendToICS(buf1);
8194         }
8195       } else {
8196         DisplayNote(message + 13);
8197       }
8198       return;
8199     }
8200     if (!strncmp(message, "tellothers ", 11)) {
8201       if (appData.icsActive) {
8202         if (loggedOn) {
8203           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8204           SendToICS(buf1);
8205         }
8206       }
8207       return;
8208     }
8209     if (!strncmp(message, "tellall ", 8)) {
8210       if (appData.icsActive) {
8211         if (loggedOn) {
8212           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8213           SendToICS(buf1);
8214         }
8215       } else {
8216         DisplayNote(message + 8);
8217       }
8218       return;
8219     }
8220     if (strncmp(message, "warning", 7) == 0) {
8221         /* Undocumented feature, use tellusererror in new code */
8222         DisplayError(message, 0);
8223         return;
8224     }
8225     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8226         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8227         strcat(realname, " query");
8228         AskQuestion(realname, buf2, buf1, cps->pr);
8229         return;
8230     }
8231     /* Commands from the engine directly to ICS.  We don't allow these to be
8232      *  sent until we are logged on. Crafty kibitzes have been known to
8233      *  interfere with the login process.
8234      */
8235     if (loggedOn) {
8236         if (!strncmp(message, "tellics ", 8)) {
8237             SendToICS(message + 8);
8238             SendToICS("\n");
8239             return;
8240         }
8241         if (!strncmp(message, "tellicsnoalias ", 15)) {
8242             SendToICS(ics_prefix);
8243             SendToICS(message + 15);
8244             SendToICS("\n");
8245             return;
8246         }
8247         /* The following are for backward compatibility only */
8248         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8249             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8250             SendToICS(ics_prefix);
8251             SendToICS(message);
8252             SendToICS("\n");
8253             return;
8254         }
8255     }
8256     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8257         return;
8258     }
8259     /*
8260      * If the move is illegal, cancel it and redraw the board.
8261      * Also deal with other error cases.  Matching is rather loose
8262      * here to accommodate engines written before the spec.
8263      */
8264     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8265         strncmp(message, "Error", 5) == 0) {
8266         if (StrStr(message, "name") ||
8267             StrStr(message, "rating") || StrStr(message, "?") ||
8268             StrStr(message, "result") || StrStr(message, "board") ||
8269             StrStr(message, "bk") || StrStr(message, "computer") ||
8270             StrStr(message, "variant") || StrStr(message, "hint") ||
8271             StrStr(message, "random") || StrStr(message, "depth") ||
8272             StrStr(message, "accepted")) {
8273             return;
8274         }
8275         if (StrStr(message, "protover")) {
8276           /* Program is responding to input, so it's apparently done
8277              initializing, and this error message indicates it is
8278              protocol version 1.  So we don't need to wait any longer
8279              for it to initialize and send feature commands. */
8280           FeatureDone(cps, 1);
8281           cps->protocolVersion = 1;
8282           return;
8283         }
8284         cps->maybeThinking = FALSE;
8285
8286         if (StrStr(message, "draw")) {
8287             /* Program doesn't have "draw" command */
8288             cps->sendDrawOffers = 0;
8289             return;
8290         }
8291         if (cps->sendTime != 1 &&
8292             (StrStr(message, "time") || StrStr(message, "otim"))) {
8293           /* Program apparently doesn't have "time" or "otim" command */
8294           cps->sendTime = 0;
8295           return;
8296         }
8297         if (StrStr(message, "analyze")) {
8298             cps->analysisSupport = FALSE;
8299             cps->analyzing = FALSE;
8300 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8301             EditGameEvent(); // [HGM] try to preserve loaded game
8302             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8303             DisplayError(buf2, 0);
8304             return;
8305         }
8306         if (StrStr(message, "(no matching move)st")) {
8307           /* Special kludge for GNU Chess 4 only */
8308           cps->stKludge = TRUE;
8309           SendTimeControl(cps, movesPerSession, timeControl,
8310                           timeIncrement, appData.searchDepth,
8311                           searchTime);
8312           return;
8313         }
8314         if (StrStr(message, "(no matching move)sd")) {
8315           /* Special kludge for GNU Chess 4 only */
8316           cps->sdKludge = TRUE;
8317           SendTimeControl(cps, movesPerSession, timeControl,
8318                           timeIncrement, appData.searchDepth,
8319                           searchTime);
8320           return;
8321         }
8322         if (!StrStr(message, "llegal")) {
8323             return;
8324         }
8325         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8326             gameMode == IcsIdle) return;
8327         if (forwardMostMove <= backwardMostMove) return;
8328         if (pausing) PauseEvent();
8329       if(appData.forceIllegal) {
8330             // [HGM] illegal: machine refused move; force position after move into it
8331           SendToProgram("force\n", cps);
8332           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8333                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8334                 // when black is to move, while there might be nothing on a2 or black
8335                 // might already have the move. So send the board as if white has the move.
8336                 // But first we must change the stm of the engine, as it refused the last move
8337                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8338                 if(WhiteOnMove(forwardMostMove)) {
8339                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8340                     SendBoard(cps, forwardMostMove); // kludgeless board
8341                 } else {
8342                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8343                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8344                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8345                 }
8346           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8347             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8348                  gameMode == TwoMachinesPlay)
8349               SendToProgram("go\n", cps);
8350             return;
8351       } else
8352         if (gameMode == PlayFromGameFile) {
8353             /* Stop reading this game file */
8354             gameMode = EditGame;
8355             ModeHighlight();
8356         }
8357         /* [HGM] illegal-move claim should forfeit game when Xboard */
8358         /* only passes fully legal moves                            */
8359         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8360             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8361                                 "False illegal-move claim", GE_XBOARD );
8362             return; // do not take back move we tested as valid
8363         }
8364         currentMove = forwardMostMove-1;
8365         DisplayMove(currentMove-1); /* before DisplayMoveError */
8366         SwitchClocks(forwardMostMove-1); // [HGM] race
8367         DisplayBothClocks();
8368         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8369                 parseList[currentMove], _(cps->which));
8370         DisplayMoveError(buf1);
8371         DrawPosition(FALSE, boards[currentMove]);
8372
8373         SetUserThinkingEnables();
8374         return;
8375     }
8376     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8377         /* Program has a broken "time" command that
8378            outputs a string not ending in newline.
8379            Don't use it. */
8380         cps->sendTime = 0;
8381     }
8382
8383     /*
8384      * If chess program startup fails, exit with an error message.
8385      * Attempts to recover here are futile.
8386      */
8387     if ((StrStr(message, "unknown host") != NULL)
8388         || (StrStr(message, "No remote directory") != NULL)
8389         || (StrStr(message, "not found") != NULL)
8390         || (StrStr(message, "No such file") != NULL)
8391         || (StrStr(message, "can't alloc") != NULL)
8392         || (StrStr(message, "Permission denied") != NULL)) {
8393
8394         cps->maybeThinking = FALSE;
8395         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8396                 _(cps->which), cps->program, cps->host, message);
8397         RemoveInputSource(cps->isr);
8398         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8399             if(cps == &first) appData.noChessProgram = TRUE;
8400             DisplayError(buf1, 0);
8401         }
8402         return;
8403     }
8404
8405     /*
8406      * Look for hint output
8407      */
8408     if (sscanf(message, "Hint: %s", buf1) == 1) {
8409         if (cps == &first && hintRequested) {
8410             hintRequested = FALSE;
8411             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8412                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8413                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8414                                     PosFlags(forwardMostMove),
8415                                     fromY, fromX, toY, toX, promoChar, buf1);
8416                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8417                 DisplayInformation(buf2);
8418             } else {
8419                 /* Hint move could not be parsed!? */
8420               snprintf(buf2, sizeof(buf2),
8421                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8422                         buf1, _(cps->which));
8423                 DisplayError(buf2, 0);
8424             }
8425         } else {
8426           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8427         }
8428         return;
8429     }
8430
8431     /*
8432      * Ignore other messages if game is not in progress
8433      */
8434     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8435         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8436
8437     /*
8438      * look for win, lose, draw, or draw offer
8439      */
8440     if (strncmp(message, "1-0", 3) == 0) {
8441         char *p, *q, *r = "";
8442         p = strchr(message, '{');
8443         if (p) {
8444             q = strchr(p, '}');
8445             if (q) {
8446                 *q = NULLCHAR;
8447                 r = p + 1;
8448             }
8449         }
8450         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8451         return;
8452     } else if (strncmp(message, "0-1", 3) == 0) {
8453         char *p, *q, *r = "";
8454         p = strchr(message, '{');
8455         if (p) {
8456             q = strchr(p, '}');
8457             if (q) {
8458                 *q = NULLCHAR;
8459                 r = p + 1;
8460             }
8461         }
8462         /* Kludge for Arasan 4.1 bug */
8463         if (strcmp(r, "Black resigns") == 0) {
8464             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8465             return;
8466         }
8467         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strncmp(message, "1/2", 3) == 0) {
8470         char *p, *q, *r = "";
8471         p = strchr(message, '{');
8472         if (p) {
8473             q = strchr(p, '}');
8474             if (q) {
8475                 *q = NULLCHAR;
8476                 r = p + 1;
8477             }
8478         }
8479
8480         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8481         return;
8482
8483     } else if (strncmp(message, "White resign", 12) == 0) {
8484         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8485         return;
8486     } else if (strncmp(message, "Black resign", 12) == 0) {
8487         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8488         return;
8489     } else if (strncmp(message, "White matches", 13) == 0 ||
8490                strncmp(message, "Black matches", 13) == 0   ) {
8491         /* [HGM] ignore GNUShogi noises */
8492         return;
8493     } else if (strncmp(message, "White", 5) == 0 &&
8494                message[5] != '(' &&
8495                StrStr(message, "Black") == NULL) {
8496         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8497         return;
8498     } else if (strncmp(message, "Black", 5) == 0 &&
8499                message[5] != '(') {
8500         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8501         return;
8502     } else if (strcmp(message, "resign") == 0 ||
8503                strcmp(message, "computer resigns") == 0) {
8504         switch (gameMode) {
8505           case MachinePlaysBlack:
8506           case IcsPlayingBlack:
8507             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8508             break;
8509           case MachinePlaysWhite:
8510           case IcsPlayingWhite:
8511             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8512             break;
8513           case TwoMachinesPlay:
8514             if (cps->twoMachinesColor[0] == 'w')
8515               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8516             else
8517               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8518             break;
8519           default:
8520             /* can't happen */
8521             break;
8522         }
8523         return;
8524     } else if (strncmp(message, "opponent mates", 14) == 0) {
8525         switch (gameMode) {
8526           case MachinePlaysBlack:
8527           case IcsPlayingBlack:
8528             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8529             break;
8530           case MachinePlaysWhite:
8531           case IcsPlayingWhite:
8532             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8533             break;
8534           case TwoMachinesPlay:
8535             if (cps->twoMachinesColor[0] == 'w')
8536               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8537             else
8538               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539             break;
8540           default:
8541             /* can't happen */
8542             break;
8543         }
8544         return;
8545     } else if (strncmp(message, "computer mates", 14) == 0) {
8546         switch (gameMode) {
8547           case MachinePlaysBlack:
8548           case IcsPlayingBlack:
8549             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8550             break;
8551           case MachinePlaysWhite:
8552           case IcsPlayingWhite:
8553             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8554             break;
8555           case TwoMachinesPlay:
8556             if (cps->twoMachinesColor[0] == 'w')
8557               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8558             else
8559               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8560             break;
8561           default:
8562             /* can't happen */
8563             break;
8564         }
8565         return;
8566     } else if (strncmp(message, "checkmate", 9) == 0) {
8567         if (WhiteOnMove(forwardMostMove)) {
8568             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8569         } else {
8570             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8571         }
8572         return;
8573     } else if (strstr(message, "Draw") != NULL ||
8574                strstr(message, "game is a draw") != NULL) {
8575         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8576         return;
8577     } else if (strstr(message, "offer") != NULL &&
8578                strstr(message, "draw") != NULL) {
8579 #if ZIPPY
8580         if (appData.zippyPlay && first.initDone) {
8581             /* Relay offer to ICS */
8582             SendToICS(ics_prefix);
8583             SendToICS("draw\n");
8584         }
8585 #endif
8586         cps->offeredDraw = 2; /* valid until this engine moves twice */
8587         if (gameMode == TwoMachinesPlay) {
8588             if (cps->other->offeredDraw) {
8589                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8590             /* [HGM] in two-machine mode we delay relaying draw offer      */
8591             /* until after we also have move, to see if it is really claim */
8592             }
8593         } else if (gameMode == MachinePlaysWhite ||
8594                    gameMode == MachinePlaysBlack) {
8595           if (userOfferedDraw) {
8596             DisplayInformation(_("Machine accepts your draw offer"));
8597             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8598           } else {
8599             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8600           }
8601         }
8602     }
8603
8604
8605     /*
8606      * Look for thinking output
8607      */
8608     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8609           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8610                                 ) {
8611         int plylev, mvleft, mvtot, curscore, time;
8612         char mvname[MOVE_LEN];
8613         u64 nodes; // [DM]
8614         char plyext;
8615         int ignore = FALSE;
8616         int prefixHint = FALSE;
8617         mvname[0] = NULLCHAR;
8618
8619         switch (gameMode) {
8620           case MachinePlaysBlack:
8621           case IcsPlayingBlack:
8622             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8623             break;
8624           case MachinePlaysWhite:
8625           case IcsPlayingWhite:
8626             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8627             break;
8628           case AnalyzeMode:
8629           case AnalyzeFile:
8630             break;
8631           case IcsObserving: /* [DM] icsEngineAnalyze */
8632             if (!appData.icsEngineAnalyze) ignore = TRUE;
8633             break;
8634           case TwoMachinesPlay:
8635             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8636                 ignore = TRUE;
8637             }
8638             break;
8639           default:
8640             ignore = TRUE;
8641             break;
8642         }
8643
8644         if (!ignore) {
8645             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8646             buf1[0] = NULLCHAR;
8647             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8648                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8649
8650                 if (plyext != ' ' && plyext != '\t') {
8651                     time *= 100;
8652                 }
8653
8654                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8655                 if( cps->scoreIsAbsolute &&
8656                     ( gameMode == MachinePlaysBlack ||
8657                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8658                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8659                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8660                      !WhiteOnMove(currentMove)
8661                     ) )
8662                 {
8663                     curscore = -curscore;
8664                 }
8665
8666                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8667
8668                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8669                         char buf[MSG_SIZ];
8670                         FILE *f;
8671                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8672                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8673                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8674                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8675                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8676                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8677                                 fclose(f);
8678                         } else DisplayError(_("failed writing PV"), 0);
8679                 }
8680
8681                 tempStats.depth = plylev;
8682                 tempStats.nodes = nodes;
8683                 tempStats.time = time;
8684                 tempStats.score = curscore;
8685                 tempStats.got_only_move = 0;
8686
8687                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8688                         int ticklen;
8689
8690                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8691                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8692                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8693                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8694                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8695                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8696                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8697                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8698                 }
8699
8700                 /* Buffer overflow protection */
8701                 if (pv[0] != NULLCHAR) {
8702                     if (strlen(pv) >= sizeof(tempStats.movelist)
8703                         && appData.debugMode) {
8704                         fprintf(debugFP,
8705                                 "PV is too long; using the first %u bytes.\n",
8706                                 (unsigned) sizeof(tempStats.movelist) - 1);
8707                     }
8708
8709                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8710                 } else {
8711                     sprintf(tempStats.movelist, " no PV\n");
8712                 }
8713
8714                 if (tempStats.seen_stat) {
8715                     tempStats.ok_to_send = 1;
8716                 }
8717
8718                 if (strchr(tempStats.movelist, '(') != NULL) {
8719                     tempStats.line_is_book = 1;
8720                     tempStats.nr_moves = 0;
8721                     tempStats.moves_left = 0;
8722                 } else {
8723                     tempStats.line_is_book = 0;
8724                 }
8725
8726                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8727                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8728
8729                 SendProgramStatsToFrontend( cps, &tempStats );
8730
8731                 /*
8732                     [AS] Protect the thinkOutput buffer from overflow... this
8733                     is only useful if buf1 hasn't overflowed first!
8734                 */
8735                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8736                          plylev,
8737                          (gameMode == TwoMachinesPlay ?
8738                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8739                          ((double) curscore) / 100.0,
8740                          prefixHint ? lastHint : "",
8741                          prefixHint ? " " : "" );
8742
8743                 if( buf1[0] != NULLCHAR ) {
8744                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8745
8746                     if( strlen(pv) > max_len ) {
8747                         if( appData.debugMode) {
8748                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8749                         }
8750                         pv[max_len+1] = '\0';
8751                     }
8752
8753                     strcat( thinkOutput, pv);
8754                 }
8755
8756                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8757                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8758                     DisplayMove(currentMove - 1);
8759                 }
8760                 return;
8761
8762             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8763                 /* crafty (9.25+) says "(only move) <move>"
8764                  * if there is only 1 legal move
8765                  */
8766                 sscanf(p, "(only move) %s", buf1);
8767                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8768                 sprintf(programStats.movelist, "%s (only move)", buf1);
8769                 programStats.depth = 1;
8770                 programStats.nr_moves = 1;
8771                 programStats.moves_left = 1;
8772                 programStats.nodes = 1;
8773                 programStats.time = 1;
8774                 programStats.got_only_move = 1;
8775
8776                 /* Not really, but we also use this member to
8777                    mean "line isn't going to change" (Crafty
8778                    isn't searching, so stats won't change) */
8779                 programStats.line_is_book = 1;
8780
8781                 SendProgramStatsToFrontend( cps, &programStats );
8782
8783                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8784                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8785                     DisplayMove(currentMove - 1);
8786                 }
8787                 return;
8788             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8789                               &time, &nodes, &plylev, &mvleft,
8790                               &mvtot, mvname) >= 5) {
8791                 /* The stat01: line is from Crafty (9.29+) in response
8792                    to the "." command */
8793                 programStats.seen_stat = 1;
8794                 cps->maybeThinking = TRUE;
8795
8796                 if (programStats.got_only_move || !appData.periodicUpdates)
8797                   return;
8798
8799                 programStats.depth = plylev;
8800                 programStats.time = time;
8801                 programStats.nodes = nodes;
8802                 programStats.moves_left = mvleft;
8803                 programStats.nr_moves = mvtot;
8804                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8805                 programStats.ok_to_send = 1;
8806                 programStats.movelist[0] = '\0';
8807
8808                 SendProgramStatsToFrontend( cps, &programStats );
8809
8810                 return;
8811
8812             } else if (strncmp(message,"++",2) == 0) {
8813                 /* Crafty 9.29+ outputs this */
8814                 programStats.got_fail = 2;
8815                 return;
8816
8817             } else if (strncmp(message,"--",2) == 0) {
8818                 /* Crafty 9.29+ outputs this */
8819                 programStats.got_fail = 1;
8820                 return;
8821
8822             } else if (thinkOutput[0] != NULLCHAR &&
8823                        strncmp(message, "    ", 4) == 0) {
8824                 unsigned message_len;
8825
8826                 p = message;
8827                 while (*p && *p == ' ') p++;
8828
8829                 message_len = strlen( p );
8830
8831                 /* [AS] Avoid buffer overflow */
8832                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8833                     strcat(thinkOutput, " ");
8834                     strcat(thinkOutput, p);
8835                 }
8836
8837                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8838                     strcat(programStats.movelist, " ");
8839                     strcat(programStats.movelist, p);
8840                 }
8841
8842                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8843                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8844                     DisplayMove(currentMove - 1);
8845                 }
8846                 return;
8847             }
8848         }
8849         else {
8850             buf1[0] = NULLCHAR;
8851
8852             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8853                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8854             {
8855                 ChessProgramStats cpstats;
8856
8857                 if (plyext != ' ' && plyext != '\t') {
8858                     time *= 100;
8859                 }
8860
8861                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8862                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8863                     curscore = -curscore;
8864                 }
8865
8866                 cpstats.depth = plylev;
8867                 cpstats.nodes = nodes;
8868                 cpstats.time = time;
8869                 cpstats.score = curscore;
8870                 cpstats.got_only_move = 0;
8871                 cpstats.movelist[0] = '\0';
8872
8873                 if (buf1[0] != NULLCHAR) {
8874                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8875                 }
8876
8877                 cpstats.ok_to_send = 0;
8878                 cpstats.line_is_book = 0;
8879                 cpstats.nr_moves = 0;
8880                 cpstats.moves_left = 0;
8881
8882                 SendProgramStatsToFrontend( cps, &cpstats );
8883             }
8884         }
8885     }
8886 }
8887
8888
8889 /* Parse a game score from the character string "game", and
8890    record it as the history of the current game.  The game
8891    score is NOT assumed to start from the standard position.
8892    The display is not updated in any way.
8893    */
8894 void
8895 ParseGameHistory(game)
8896      char *game;
8897 {
8898     ChessMove moveType;
8899     int fromX, fromY, toX, toY, boardIndex;
8900     char promoChar;
8901     char *p, *q;
8902     char buf[MSG_SIZ];
8903
8904     if (appData.debugMode)
8905       fprintf(debugFP, "Parsing game history: %s\n", game);
8906
8907     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8908     gameInfo.site = StrSave(appData.icsHost);
8909     gameInfo.date = PGNDate();
8910     gameInfo.round = StrSave("-");
8911
8912     /* Parse out names of players */
8913     while (*game == ' ') game++;
8914     p = buf;
8915     while (*game != ' ') *p++ = *game++;
8916     *p = NULLCHAR;
8917     gameInfo.white = StrSave(buf);
8918     while (*game == ' ') game++;
8919     p = buf;
8920     while (*game != ' ' && *game != '\n') *p++ = *game++;
8921     *p = NULLCHAR;
8922     gameInfo.black = StrSave(buf);
8923
8924     /* Parse moves */
8925     boardIndex = blackPlaysFirst ? 1 : 0;
8926     yynewstr(game);
8927     for (;;) {
8928         yyboardindex = boardIndex;
8929         moveType = (ChessMove) Myylex();
8930         switch (moveType) {
8931           case IllegalMove:             /* maybe suicide chess, etc. */
8932   if (appData.debugMode) {
8933     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8935     setbuf(debugFP, NULL);
8936   }
8937           case WhitePromotion:
8938           case BlackPromotion:
8939           case WhiteNonPromotion:
8940           case BlackNonPromotion:
8941           case NormalMove:
8942           case WhiteCapturesEnPassant:
8943           case BlackCapturesEnPassant:
8944           case WhiteKingSideCastle:
8945           case WhiteQueenSideCastle:
8946           case BlackKingSideCastle:
8947           case BlackQueenSideCastle:
8948           case WhiteKingSideCastleWild:
8949           case WhiteQueenSideCastleWild:
8950           case BlackKingSideCastleWild:
8951           case BlackQueenSideCastleWild:
8952           /* PUSH Fabien */
8953           case WhiteHSideCastleFR:
8954           case WhiteASideCastleFR:
8955           case BlackHSideCastleFR:
8956           case BlackASideCastleFR:
8957           /* POP Fabien */
8958             fromX = currentMoveString[0] - AAA;
8959             fromY = currentMoveString[1] - ONE;
8960             toX = currentMoveString[2] - AAA;
8961             toY = currentMoveString[3] - ONE;
8962             promoChar = currentMoveString[4];
8963             break;
8964           case WhiteDrop:
8965           case BlackDrop:
8966             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8967             fromX = moveType == WhiteDrop ?
8968               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8969             (int) CharToPiece(ToLower(currentMoveString[0]));
8970             fromY = DROP_RANK;
8971             toX = currentMoveString[2] - AAA;
8972             toY = currentMoveString[3] - ONE;
8973             promoChar = NULLCHAR;
8974             break;
8975           case AmbiguousMove:
8976             /* bug? */
8977             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8978   if (appData.debugMode) {
8979     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8980     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8981     setbuf(debugFP, NULL);
8982   }
8983             DisplayError(buf, 0);
8984             return;
8985           case ImpossibleMove:
8986             /* bug? */
8987             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8988   if (appData.debugMode) {
8989     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8990     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8991     setbuf(debugFP, NULL);
8992   }
8993             DisplayError(buf, 0);
8994             return;
8995           case EndOfFile:
8996             if (boardIndex < backwardMostMove) {
8997                 /* Oops, gap.  How did that happen? */
8998                 DisplayError(_("Gap in move list"), 0);
8999                 return;
9000             }
9001             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9002             if (boardIndex > forwardMostMove) {
9003                 forwardMostMove = boardIndex;
9004             }
9005             return;
9006           case ElapsedTime:
9007             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9008                 strcat(parseList[boardIndex-1], " ");
9009                 strcat(parseList[boardIndex-1], yy_text);
9010             }
9011             continue;
9012           case Comment:
9013           case PGNTag:
9014           case NAG:
9015           default:
9016             /* ignore */
9017             continue;
9018           case WhiteWins:
9019           case BlackWins:
9020           case GameIsDrawn:
9021           case GameUnfinished:
9022             if (gameMode == IcsExamining) {
9023                 if (boardIndex < backwardMostMove) {
9024                     /* Oops, gap.  How did that happen? */
9025                     return;
9026                 }
9027                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9028                 return;
9029             }
9030             gameInfo.result = moveType;
9031             p = strchr(yy_text, '{');
9032             if (p == NULL) p = strchr(yy_text, '(');
9033             if (p == NULL) {
9034                 p = yy_text;
9035                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9036             } else {
9037                 q = strchr(p, *p == '{' ? '}' : ')');
9038                 if (q != NULL) *q = NULLCHAR;
9039                 p++;
9040             }
9041             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9042             gameInfo.resultDetails = StrSave(p);
9043             continue;
9044         }
9045         if (boardIndex >= forwardMostMove &&
9046             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9047             backwardMostMove = blackPlaysFirst ? 1 : 0;
9048             return;
9049         }
9050         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9051                                  fromY, fromX, toY, toX, promoChar,
9052                                  parseList[boardIndex]);
9053         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9054         /* currentMoveString is set as a side-effect of yylex */
9055         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9056         strcat(moveList[boardIndex], "\n");
9057         boardIndex++;
9058         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9059         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9060           case MT_NONE:
9061           case MT_STALEMATE:
9062           default:
9063             break;
9064           case MT_CHECK:
9065             if(gameInfo.variant != VariantShogi)
9066                 strcat(parseList[boardIndex - 1], "+");
9067             break;
9068           case MT_CHECKMATE:
9069           case MT_STAINMATE:
9070             strcat(parseList[boardIndex - 1], "#");
9071             break;
9072         }
9073     }
9074 }
9075
9076
9077 /* Apply a move to the given board  */
9078 void
9079 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9080      int fromX, fromY, toX, toY;
9081      int promoChar;
9082      Board board;
9083 {
9084   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9085   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9086
9087     /* [HGM] compute & store e.p. status and castling rights for new position */
9088     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9089
9090       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9091       oldEP = (signed char)board[EP_STATUS];
9092       board[EP_STATUS] = EP_NONE;
9093
9094   if (fromY == DROP_RANK) {
9095         /* must be first */
9096         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9097             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9098             return;
9099         }
9100         piece = board[toY][toX] = (ChessSquare) fromX;
9101   } else {
9102       int i;
9103
9104       if( board[toY][toX] != EmptySquare )
9105            board[EP_STATUS] = EP_CAPTURE;
9106
9107       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9108            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9109                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9110       } else
9111       if( board[fromY][fromX] == WhitePawn ) {
9112            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9113                board[EP_STATUS] = EP_PAWN_MOVE;
9114            if( toY-fromY==2) {
9115                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9116                         gameInfo.variant != VariantBerolina || toX < fromX)
9117                       board[EP_STATUS] = toX | berolina;
9118                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9119                         gameInfo.variant != VariantBerolina || toX > fromX)
9120                       board[EP_STATUS] = toX;
9121            }
9122       } else
9123       if( board[fromY][fromX] == BlackPawn ) {
9124            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9125                board[EP_STATUS] = EP_PAWN_MOVE;
9126            if( toY-fromY== -2) {
9127                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9128                         gameInfo.variant != VariantBerolina || toX < fromX)
9129                       board[EP_STATUS] = toX | berolina;
9130                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9131                         gameInfo.variant != VariantBerolina || toX > fromX)
9132                       board[EP_STATUS] = toX;
9133            }
9134        }
9135
9136        for(i=0; i<nrCastlingRights; i++) {
9137            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9138               board[CASTLING][i] == toX   && castlingRank[i] == toY
9139              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9140        }
9141
9142      if (fromX == toX && fromY == toY) return;
9143
9144      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9145      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9146      if(gameInfo.variant == VariantKnightmate)
9147          king += (int) WhiteUnicorn - (int) WhiteKing;
9148
9149     /* Code added by Tord: */
9150     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9151     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9152         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9153       board[fromY][fromX] = EmptySquare;
9154       board[toY][toX] = EmptySquare;
9155       if((toX > fromX) != (piece == WhiteRook)) {
9156         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9157       } else {
9158         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9159       }
9160     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9161                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9162       board[fromY][fromX] = EmptySquare;
9163       board[toY][toX] = EmptySquare;
9164       if((toX > fromX) != (piece == BlackRook)) {
9165         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9166       } else {
9167         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9168       }
9169     /* End of code added by Tord */
9170
9171     } else if (board[fromY][fromX] == king
9172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9173         && toY == fromY && toX > fromX+1) {
9174         board[fromY][fromX] = EmptySquare;
9175         board[toY][toX] = king;
9176         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9177         board[fromY][BOARD_RGHT-1] = EmptySquare;
9178     } else if (board[fromY][fromX] == king
9179         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9180                && toY == fromY && toX < fromX-1) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = king;
9183         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9184         board[fromY][BOARD_LEFT] = EmptySquare;
9185     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9186                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9187                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9188                ) {
9189         /* white pawn promotion */
9190         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9191         if(gameInfo.variant==VariantBughouse ||
9192            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9193             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9194         board[fromY][fromX] = EmptySquare;
9195     } else if ((fromY >= BOARD_HEIGHT>>1)
9196                && (toX != fromX)
9197                && gameInfo.variant != VariantXiangqi
9198                && gameInfo.variant != VariantBerolina
9199                && (board[fromY][fromX] == WhitePawn)
9200                && (board[toY][toX] == EmptySquare)) {
9201         board[fromY][fromX] = EmptySquare;
9202         board[toY][toX] = WhitePawn;
9203         captured = board[toY - 1][toX];
9204         board[toY - 1][toX] = EmptySquare;
9205     } else if ((fromY == BOARD_HEIGHT-4)
9206                && (toX == fromX)
9207                && gameInfo.variant == VariantBerolina
9208                && (board[fromY][fromX] == WhitePawn)
9209                && (board[toY][toX] == EmptySquare)) {
9210         board[fromY][fromX] = EmptySquare;
9211         board[toY][toX] = WhitePawn;
9212         if(oldEP & EP_BEROLIN_A) {
9213                 captured = board[fromY][fromX-1];
9214                 board[fromY][fromX-1] = EmptySquare;
9215         }else{  captured = board[fromY][fromX+1];
9216                 board[fromY][fromX+1] = EmptySquare;
9217         }
9218     } else if (board[fromY][fromX] == king
9219         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9220                && toY == fromY && toX > fromX+1) {
9221         board[fromY][fromX] = EmptySquare;
9222         board[toY][toX] = king;
9223         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9224         board[fromY][BOARD_RGHT-1] = EmptySquare;
9225     } else if (board[fromY][fromX] == king
9226         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9227                && toY == fromY && toX < fromX-1) {
9228         board[fromY][fromX] = EmptySquare;
9229         board[toY][toX] = king;
9230         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9231         board[fromY][BOARD_LEFT] = EmptySquare;
9232     } else if (fromY == 7 && fromX == 3
9233                && board[fromY][fromX] == BlackKing
9234                && toY == 7 && toX == 5) {
9235         board[fromY][fromX] = EmptySquare;
9236         board[toY][toX] = BlackKing;
9237         board[fromY][7] = EmptySquare;
9238         board[toY][4] = BlackRook;
9239     } else if (fromY == 7 && fromX == 3
9240                && board[fromY][fromX] == BlackKing
9241                && toY == 7 && toX == 1) {
9242         board[fromY][fromX] = EmptySquare;
9243         board[toY][toX] = BlackKing;
9244         board[fromY][0] = EmptySquare;
9245         board[toY][2] = BlackRook;
9246     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9247                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9248                && toY < promoRank && promoChar
9249                ) {
9250         /* black pawn promotion */
9251         board[toY][toX] = CharToPiece(ToLower(promoChar));
9252         if(gameInfo.variant==VariantBughouse ||
9253            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9254             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9255         board[fromY][fromX] = EmptySquare;
9256     } else if ((fromY < BOARD_HEIGHT>>1)
9257                && (toX != fromX)
9258                && gameInfo.variant != VariantXiangqi
9259                && gameInfo.variant != VariantBerolina
9260                && (board[fromY][fromX] == BlackPawn)
9261                && (board[toY][toX] == EmptySquare)) {
9262         board[fromY][fromX] = EmptySquare;
9263         board[toY][toX] = BlackPawn;
9264         captured = board[toY + 1][toX];
9265         board[toY + 1][toX] = EmptySquare;
9266     } else if ((fromY == 3)
9267                && (toX == fromX)
9268                && gameInfo.variant == VariantBerolina
9269                && (board[fromY][fromX] == BlackPawn)
9270                && (board[toY][toX] == EmptySquare)) {
9271         board[fromY][fromX] = EmptySquare;
9272         board[toY][toX] = BlackPawn;
9273         if(oldEP & EP_BEROLIN_A) {
9274                 captured = board[fromY][fromX-1];
9275                 board[fromY][fromX-1] = EmptySquare;
9276         }else{  captured = board[fromY][fromX+1];
9277                 board[fromY][fromX+1] = EmptySquare;
9278         }
9279     } else {
9280         board[toY][toX] = board[fromY][fromX];
9281         board[fromY][fromX] = EmptySquare;
9282     }
9283   }
9284
9285     if (gameInfo.holdingsWidth != 0) {
9286
9287       /* !!A lot more code needs to be written to support holdings  */
9288       /* [HGM] OK, so I have written it. Holdings are stored in the */
9289       /* penultimate board files, so they are automaticlly stored   */
9290       /* in the game history.                                       */
9291       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9292                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9293         /* Delete from holdings, by decreasing count */
9294         /* and erasing image if necessary            */
9295         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9296         if(p < (int) BlackPawn) { /* white drop */
9297              p -= (int)WhitePawn;
9298                  p = PieceToNumber((ChessSquare)p);
9299              if(p >= gameInfo.holdingsSize) p = 0;
9300              if(--board[p][BOARD_WIDTH-2] <= 0)
9301                   board[p][BOARD_WIDTH-1] = EmptySquare;
9302              if((int)board[p][BOARD_WIDTH-2] < 0)
9303                         board[p][BOARD_WIDTH-2] = 0;
9304         } else {                  /* black drop */
9305              p -= (int)BlackPawn;
9306                  p = PieceToNumber((ChessSquare)p);
9307              if(p >= gameInfo.holdingsSize) p = 0;
9308              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9309                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9310              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9311                         board[BOARD_HEIGHT-1-p][1] = 0;
9312         }
9313       }
9314       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9315           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9316         /* [HGM] holdings: Add to holdings, if holdings exist */
9317         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9318                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9319                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9320         }
9321         p = (int) captured;
9322         if (p >= (int) BlackPawn) {
9323           p -= (int)BlackPawn;
9324           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9325                   /* in Shogi restore piece to its original  first */
9326                   captured = (ChessSquare) (DEMOTED captured);
9327                   p = DEMOTED p;
9328           }
9329           p = PieceToNumber((ChessSquare)p);
9330           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9331           board[p][BOARD_WIDTH-2]++;
9332           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9333         } else {
9334           p -= (int)WhitePawn;
9335           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9336                   captured = (ChessSquare) (DEMOTED captured);
9337                   p = DEMOTED p;
9338           }
9339           p = PieceToNumber((ChessSquare)p);
9340           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9341           board[BOARD_HEIGHT-1-p][1]++;
9342           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9343         }
9344       }
9345     } else if (gameInfo.variant == VariantAtomic) {
9346       if (captured != EmptySquare) {
9347         int y, x;
9348         for (y = toY-1; y <= toY+1; y++) {
9349           for (x = toX-1; x <= toX+1; x++) {
9350             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9351                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9352               board[y][x] = EmptySquare;
9353             }
9354           }
9355         }
9356         board[toY][toX] = EmptySquare;
9357       }
9358     }
9359     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9360         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9361     } else
9362     if(promoChar == '+') {
9363         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9364         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9365     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9366         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9367     }
9368     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9369                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9370         // [HGM] superchess: take promotion piece out of holdings
9371         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9372         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9373             if(!--board[k][BOARD_WIDTH-2])
9374                 board[k][BOARD_WIDTH-1] = EmptySquare;
9375         } else {
9376             if(!--board[BOARD_HEIGHT-1-k][1])
9377                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9378         }
9379     }
9380
9381 }
9382
9383 /* Updates forwardMostMove */
9384 void
9385 MakeMove(fromX, fromY, toX, toY, promoChar)
9386      int fromX, fromY, toX, toY;
9387      int promoChar;
9388 {
9389 //    forwardMostMove++; // [HGM] bare: moved downstream
9390
9391     (void) CoordsToAlgebraic(boards[forwardMostMove],
9392                              PosFlags(forwardMostMove),
9393                              fromY, fromX, toY, toX, promoChar,
9394                              parseList[forwardMostMove]);
9395
9396     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9397         int timeLeft; static int lastLoadFlag=0; int king, piece;
9398         piece = boards[forwardMostMove][fromY][fromX];
9399         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9400         if(gameInfo.variant == VariantKnightmate)
9401             king += (int) WhiteUnicorn - (int) WhiteKing;
9402         if(forwardMostMove == 0) {
9403             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9404                 fprintf(serverMoves, "%s;", UserName());
9405             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9406                 fprintf(serverMoves, "%s;", second.tidy);
9407             fprintf(serverMoves, "%s;", first.tidy);
9408             if(gameMode == MachinePlaysWhite)
9409                 fprintf(serverMoves, "%s;", UserName());
9410             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9411                 fprintf(serverMoves, "%s;", second.tidy);
9412         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9413         lastLoadFlag = loadFlag;
9414         // print base move
9415         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9416         // print castling suffix
9417         if( toY == fromY && piece == king ) {
9418             if(toX-fromX > 1)
9419                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9420             if(fromX-toX >1)
9421                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9422         }
9423         // e.p. suffix
9424         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9425              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9426              boards[forwardMostMove][toY][toX] == EmptySquare
9427              && fromX != toX && fromY != toY)
9428                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9429         // promotion suffix
9430         if(promoChar != NULLCHAR)
9431                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9432         if(!loadFlag) {
9433                 char buf[MOVE_LEN*2], *p; int len;
9434             fprintf(serverMoves, "/%d/%d",
9435                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9436             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9437             else                      timeLeft = blackTimeRemaining/1000;
9438             fprintf(serverMoves, "/%d", timeLeft);
9439                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9440                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9441                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9442             fprintf(serverMoves, "/%s", buf);
9443         }
9444         fflush(serverMoves);
9445     }
9446
9447     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9448         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9449       return;
9450     }
9451     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9452     if (commentList[forwardMostMove+1] != NULL) {
9453         free(commentList[forwardMostMove+1]);
9454         commentList[forwardMostMove+1] = NULL;
9455     }
9456     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9457     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9458     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9459     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9460     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9461     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9462     adjustedClock = FALSE;
9463     gameInfo.result = GameUnfinished;
9464     if (gameInfo.resultDetails != NULL) {
9465         free(gameInfo.resultDetails);
9466         gameInfo.resultDetails = NULL;
9467     }
9468     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9469                               moveList[forwardMostMove - 1]);
9470     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9471       case MT_NONE:
9472       case MT_STALEMATE:
9473       default:
9474         break;
9475       case MT_CHECK:
9476         if(gameInfo.variant != VariantShogi)
9477             strcat(parseList[forwardMostMove - 1], "+");
9478         break;
9479       case MT_CHECKMATE:
9480       case MT_STAINMATE:
9481         strcat(parseList[forwardMostMove - 1], "#");
9482         break;
9483     }
9484     if (appData.debugMode) {
9485         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9486     }
9487
9488 }
9489
9490 /* Updates currentMove if not pausing */
9491 void
9492 ShowMove(fromX, fromY, toX, toY)
9493 {
9494     int instant = (gameMode == PlayFromGameFile) ?
9495         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9496     if(appData.noGUI) return;
9497     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9498         if (!instant) {
9499             if (forwardMostMove == currentMove + 1) {
9500                 AnimateMove(boards[forwardMostMove - 1],
9501                             fromX, fromY, toX, toY);
9502             }
9503             if (appData.highlightLastMove) {
9504                 SetHighlights(fromX, fromY, toX, toY);
9505             }
9506         }
9507         currentMove = forwardMostMove;
9508     }
9509
9510     if (instant) return;
9511
9512     DisplayMove(currentMove - 1);
9513     DrawPosition(FALSE, boards[currentMove]);
9514     DisplayBothClocks();
9515     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9516 }
9517
9518 void SendEgtPath(ChessProgramState *cps)
9519 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9520         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9521
9522         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9523
9524         while(*p) {
9525             char c, *q = name+1, *r, *s;
9526
9527             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9528             while(*p && *p != ',') *q++ = *p++;
9529             *q++ = ':'; *q = 0;
9530             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9531                 strcmp(name, ",nalimov:") == 0 ) {
9532                 // take nalimov path from the menu-changeable option first, if it is defined
9533               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9534                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9535             } else
9536             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9537                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9538                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9539                 s = r = StrStr(s, ":") + 1; // beginning of path info
9540                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9541                 c = *r; *r = 0;             // temporarily null-terminate path info
9542                     *--q = 0;               // strip of trailig ':' from name
9543                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9544                 *r = c;
9545                 SendToProgram(buf,cps);     // send egtbpath command for this format
9546             }
9547             if(*p == ',') p++; // read away comma to position for next format name
9548         }
9549 }
9550
9551 void
9552 InitChessProgram(cps, setup)
9553      ChessProgramState *cps;
9554      int setup; /* [HGM] needed to setup FRC opening position */
9555 {
9556     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9557     if (appData.noChessProgram) return;
9558     hintRequested = FALSE;
9559     bookRequested = FALSE;
9560
9561     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9562     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9563     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9564     if(cps->memSize) { /* [HGM] memory */
9565       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9566         SendToProgram(buf, cps);
9567     }
9568     SendEgtPath(cps); /* [HGM] EGT */
9569     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9570       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9571         SendToProgram(buf, cps);
9572     }
9573
9574     SendToProgram(cps->initString, cps);
9575     if (gameInfo.variant != VariantNormal &&
9576         gameInfo.variant != VariantLoadable
9577         /* [HGM] also send variant if board size non-standard */
9578         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9579                                             ) {
9580       char *v = VariantName(gameInfo.variant);
9581       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9582         /* [HGM] in protocol 1 we have to assume all variants valid */
9583         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9584         DisplayFatalError(buf, 0, 1);
9585         return;
9586       }
9587
9588       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9589       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9590       if( gameInfo.variant == VariantXiangqi )
9591            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9592       if( gameInfo.variant == VariantShogi )
9593            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9594       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9595            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9596       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9597           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9598            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9599       if( gameInfo.variant == VariantCourier )
9600            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9601       if( gameInfo.variant == VariantSuper )
9602            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9603       if( gameInfo.variant == VariantGreat )
9604            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9605       if( gameInfo.variant == VariantSChess )
9606            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9607       if( gameInfo.variant == VariantGrand )
9608            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9609
9610       if(overruled) {
9611         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9612                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9613            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9614            if(StrStr(cps->variants, b) == NULL) {
9615                // specific sized variant not known, check if general sizing allowed
9616                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9617                    if(StrStr(cps->variants, "boardsize") == NULL) {
9618                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9619                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9620                        DisplayFatalError(buf, 0, 1);
9621                        return;
9622                    }
9623                    /* [HGM] here we really should compare with the maximum supported board size */
9624                }
9625            }
9626       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9627       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9628       SendToProgram(buf, cps);
9629     }
9630     currentlyInitializedVariant = gameInfo.variant;
9631
9632     /* [HGM] send opening position in FRC to first engine */
9633     if(setup) {
9634           SendToProgram("force\n", cps);
9635           SendBoard(cps, 0);
9636           /* engine is now in force mode! Set flag to wake it up after first move. */
9637           setboardSpoiledMachineBlack = 1;
9638     }
9639
9640     if (cps->sendICS) {
9641       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9642       SendToProgram(buf, cps);
9643     }
9644     cps->maybeThinking = FALSE;
9645     cps->offeredDraw = 0;
9646     if (!appData.icsActive) {
9647         SendTimeControl(cps, movesPerSession, timeControl,
9648                         timeIncrement, appData.searchDepth,
9649                         searchTime);
9650     }
9651     if (appData.showThinking
9652         // [HGM] thinking: four options require thinking output to be sent
9653         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9654                                 ) {
9655         SendToProgram("post\n", cps);
9656     }
9657     SendToProgram("hard\n", cps);
9658     if (!appData.ponderNextMove) {
9659         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9660            it without being sure what state we are in first.  "hard"
9661            is not a toggle, so that one is OK.
9662          */
9663         SendToProgram("easy\n", cps);
9664     }
9665     if (cps->usePing) {
9666       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9667       SendToProgram(buf, cps);
9668     }
9669     cps->initDone = TRUE;
9670     ClearEngineOutputPane(cps == &second);
9671 }
9672
9673
9674 void
9675 StartChessProgram(cps)
9676      ChessProgramState *cps;
9677 {
9678     char buf[MSG_SIZ];
9679     int err;
9680
9681     if (appData.noChessProgram) return;
9682     cps->initDone = FALSE;
9683
9684     if (strcmp(cps->host, "localhost") == 0) {
9685         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9686     } else if (*appData.remoteShell == NULLCHAR) {
9687         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9688     } else {
9689         if (*appData.remoteUser == NULLCHAR) {
9690           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9691                     cps->program);
9692         } else {
9693           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9694                     cps->host, appData.remoteUser, cps->program);
9695         }
9696         err = StartChildProcess(buf, "", &cps->pr);
9697     }
9698
9699     if (err != 0) {
9700       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9701         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9702         if(cps != &first) return;
9703         appData.noChessProgram = TRUE;
9704         ThawUI();
9705         SetNCPMode();
9706 //      DisplayFatalError(buf, err, 1);
9707 //      cps->pr = NoProc;
9708 //      cps->isr = NULL;
9709         return;
9710     }
9711
9712     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9713     if (cps->protocolVersion > 1) {
9714       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9715       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9716       cps->comboCnt = 0;  //                and values of combo boxes
9717       SendToProgram(buf, cps);
9718     } else {
9719       SendToProgram("xboard\n", cps);
9720     }
9721 }
9722
9723 void
9724 TwoMachinesEventIfReady P((void))
9725 {
9726   static int curMess = 0;
9727   if (first.lastPing != first.lastPong) {
9728     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9729     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9730     return;
9731   }
9732   if (second.lastPing != second.lastPong) {
9733     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9734     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9735     return;
9736   }
9737   DisplayMessage("", ""); curMess = 0;
9738   ThawUI();
9739   TwoMachinesEvent();
9740 }
9741
9742 char *
9743 MakeName(char *template)
9744 {
9745     time_t clock;
9746     struct tm *tm;
9747     static char buf[MSG_SIZ];
9748     char *p = buf;
9749     int i;
9750
9751     clock = time((time_t *)NULL);
9752     tm = localtime(&clock);
9753
9754     while(*p++ = *template++) if(p[-1] == '%') {
9755         switch(*template++) {
9756           case 0:   *p = 0; return buf;
9757           case 'Y': i = tm->tm_year+1900; break;
9758           case 'y': i = tm->tm_year-100; break;
9759           case 'M': i = tm->tm_mon+1; break;
9760           case 'd': i = tm->tm_mday; break;
9761           case 'h': i = tm->tm_hour; break;
9762           case 'm': i = tm->tm_min; break;
9763           case 's': i = tm->tm_sec; break;
9764           default:  i = 0;
9765         }
9766         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9767     }
9768     return buf;
9769 }
9770
9771 int
9772 CountPlayers(char *p)
9773 {
9774     int n = 0;
9775     while(p = strchr(p, '\n')) p++, n++; // count participants
9776     return n;
9777 }
9778
9779 FILE *
9780 WriteTourneyFile(char *results, FILE *f)
9781 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9782     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9783     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9784         // create a file with tournament description
9785         fprintf(f, "-participants {%s}\n", appData.participants);
9786         fprintf(f, "-seedBase %d\n", appData.seedBase);
9787         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9788         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9789         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9790         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9791         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9792         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9793         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9794         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9795         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9796         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9797         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9798         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9799         if(searchTime > 0)
9800                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9801         else {
9802                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9803                 fprintf(f, "-tc %s\n", appData.timeControl);
9804                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9805         }
9806         fprintf(f, "-results \"%s\"\n", results);
9807     }
9808     return f;
9809 }
9810
9811 #define MAXENGINES 1000
9812 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9813
9814 void Substitute(char *participants, int expunge)
9815 {
9816     int i, changed, changes=0, nPlayers=0;
9817     char *p, *q, *r, buf[MSG_SIZ];
9818     if(participants == NULL) return;
9819     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9820     r = p = participants; q = appData.participants;
9821     while(*p && *p == *q) {
9822         if(*p == '\n') r = p+1, nPlayers++;
9823         p++; q++;
9824     }
9825     if(*p) { // difference
9826         while(*p && *p++ != '\n');
9827         while(*q && *q++ != '\n');
9828       changed = nPlayers;
9829         changes = 1 + (strcmp(p, q) != 0);
9830     }
9831     if(changes == 1) { // a single engine mnemonic was changed
9832         q = r; while(*q) nPlayers += (*q++ == '\n');
9833         p = buf; while(*r && (*p = *r++) != '\n') p++;
9834         *p = NULLCHAR;
9835         NamesToList(firstChessProgramNames, command, mnemonic);
9836         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9837         if(mnemonic[i]) { // The substitute is valid
9838             FILE *f;
9839             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9840                 flock(fileno(f), LOCK_EX);
9841                 ParseArgsFromFile(f);
9842                 fseek(f, 0, SEEK_SET);
9843                 FREE(appData.participants); appData.participants = participants;
9844                 if(expunge) { // erase results of replaced engine
9845                     int len = strlen(appData.results), w, b, dummy;
9846                     for(i=0; i<len; i++) {
9847                         Pairing(i, nPlayers, &w, &b, &dummy);
9848                         if((w == changed || b == changed) && appData.results[i] == '*') {
9849                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9850                             fclose(f);
9851                             return;
9852                         }
9853                     }
9854                     for(i=0; i<len; i++) {
9855                         Pairing(i, nPlayers, &w, &b, &dummy);
9856                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9857                     }
9858                 }
9859                 WriteTourneyFile(appData.results, f);
9860                 fclose(f); // release lock
9861                 return;
9862             }
9863         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9864     }
9865     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9866     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9867     free(participants);
9868     return;
9869 }
9870
9871 int
9872 CreateTourney(char *name)
9873 {
9874         FILE *f;
9875         if(matchMode && strcmp(name, appData.tourneyFile)) {
9876              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9877         }
9878         if(name[0] == NULLCHAR) {
9879             if(appData.participants[0])
9880                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9881             return 0;
9882         }
9883         f = fopen(name, "r");
9884         if(f) { // file exists
9885             ASSIGN(appData.tourneyFile, name);
9886             ParseArgsFromFile(f); // parse it
9887         } else {
9888             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9889             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9890                 DisplayError(_("Not enough participants"), 0);
9891                 return 0;
9892             }
9893             ASSIGN(appData.tourneyFile, name);
9894             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9895             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9896         }
9897         fclose(f);
9898         appData.noChessProgram = FALSE;
9899         appData.clockMode = TRUE;
9900         SetGNUMode();
9901         return 1;
9902 }
9903
9904 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9905 {
9906     char buf[MSG_SIZ], *p, *q;
9907     int i=1;
9908     while(*names) {
9909         p = names; q = buf;
9910         while(*p && *p != '\n') *q++ = *p++;
9911         *q = 0;
9912         if(engineList[i]) free(engineList[i]);
9913         engineList[i] = strdup(buf);
9914         if(*p == '\n') p++;
9915         TidyProgramName(engineList[i], "localhost", buf);
9916         if(engineMnemonic[i]) free(engineMnemonic[i]);
9917         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9918             strcat(buf, " (");
9919             sscanf(q + 8, "%s", buf + strlen(buf));
9920             strcat(buf, ")");
9921         }
9922         engineMnemonic[i] = strdup(buf);
9923         names = p; i++;
9924       if(i > MAXENGINES - 2) break;
9925     }
9926     engineList[i] = engineMnemonic[i] = NULL;
9927 }
9928
9929 // following implemented as macro to avoid type limitations
9930 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9931
9932 void SwapEngines(int n)
9933 {   // swap settings for first engine and other engine (so far only some selected options)
9934     int h;
9935     char *p;
9936     if(n == 0) return;
9937     SWAP(directory, p)
9938     SWAP(chessProgram, p)
9939     SWAP(isUCI, h)
9940     SWAP(hasOwnBookUCI, h)
9941     SWAP(protocolVersion, h)
9942     SWAP(reuse, h)
9943     SWAP(scoreIsAbsolute, h)
9944     SWAP(timeOdds, h)
9945     SWAP(logo, p)
9946     SWAP(pgnName, p)
9947     SWAP(pvSAN, h)
9948     SWAP(engOptions, p)
9949 }
9950
9951 void
9952 SetPlayer(int player)
9953 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9954     int i;
9955     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9956     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9957     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9958     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9959     if(mnemonic[i]) {
9960         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9961         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9962         appData.firstHasOwnBookUCI = !appData.defNoBook;
9963         ParseArgsFromString(buf);
9964     }
9965     free(engineName);
9966 }
9967
9968 int
9969 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9970 {   // determine players from game number
9971     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9972
9973     if(appData.tourneyType == 0) {
9974         roundsPerCycle = (nPlayers - 1) | 1;
9975         pairingsPerRound = nPlayers / 2;
9976     } else if(appData.tourneyType > 0) {
9977         roundsPerCycle = nPlayers - appData.tourneyType;
9978         pairingsPerRound = appData.tourneyType;
9979     }
9980     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9981     gamesPerCycle = gamesPerRound * roundsPerCycle;
9982     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9983     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9984     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9985     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9986     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9987     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9988
9989     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9990     if(appData.roundSync) *syncInterval = gamesPerRound;
9991
9992     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9993
9994     if(appData.tourneyType == 0) {
9995         if(curPairing == (nPlayers-1)/2 ) {
9996             *whitePlayer = curRound;
9997             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9998         } else {
9999             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10000             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10001             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10002             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10003         }
10004     } else if(appData.tourneyType > 0) {
10005         *whitePlayer = curPairing;
10006         *blackPlayer = curRound + appData.tourneyType;
10007     }
10008
10009     // take care of white/black alternation per round. 
10010     // For cycles and games this is already taken care of by default, derived from matchGame!
10011     return curRound & 1;
10012 }
10013
10014 int
10015 NextTourneyGame(int nr, int *swapColors)
10016 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10017     char *p, *q;
10018     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10019     FILE *tf;
10020     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10021     tf = fopen(appData.tourneyFile, "r");
10022     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10023     ParseArgsFromFile(tf); fclose(tf);
10024     InitTimeControls(); // TC might be altered from tourney file
10025
10026     nPlayers = CountPlayers(appData.participants); // count participants
10027     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10028     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10029
10030     if(syncInterval) {
10031         p = q = appData.results;
10032         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10033         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10034             DisplayMessage(_("Waiting for other game(s)"),"");
10035             waitingForGame = TRUE;
10036             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10037             return 0;
10038         }
10039         waitingForGame = FALSE;
10040     }
10041
10042     if(appData.tourneyType < 0) {
10043         if(nr>=0 && !pairingReceived) {
10044             char buf[1<<16];
10045             if(pairing.pr == NoProc) {
10046                 if(!appData.pairingEngine[0]) {
10047                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10048                     return 0;
10049                 }
10050                 StartChessProgram(&pairing); // starts the pairing engine
10051             }
10052             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10053             SendToProgram(buf, &pairing);
10054             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10055             SendToProgram(buf, &pairing);
10056             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10057         }
10058         pairingReceived = 0;                              // ... so we continue here 
10059         *swapColors = 0;
10060         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10061         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10062         matchGame = 1; roundNr = nr / syncInterval + 1;
10063     }
10064
10065     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10066
10067     // redefine engines, engine dir, etc.
10068     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10069     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10070     SwapEngines(1);
10071     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10072     SwapEngines(1);         // and make that valid for second engine by swapping
10073     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10074     InitEngine(&second, 1);
10075     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10076     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10077     return 1;
10078 }
10079
10080 void
10081 NextMatchGame()
10082 {   // performs game initialization that does not invoke engines, and then tries to start the game
10083     int res, firstWhite, swapColors = 0;
10084     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10085     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10086     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10087     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10088     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10089     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10090     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10091     Reset(FALSE, first.pr != NoProc);
10092     res = LoadGameOrPosition(matchGame); // setup game
10093     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10094     if(!res) return; // abort when bad game/pos file
10095     TwoMachinesEvent();
10096 }
10097
10098 void UserAdjudicationEvent( int result )
10099 {
10100     ChessMove gameResult = GameIsDrawn;
10101
10102     if( result > 0 ) {
10103         gameResult = WhiteWins;
10104     }
10105     else if( result < 0 ) {
10106         gameResult = BlackWins;
10107     }
10108
10109     if( gameMode == TwoMachinesPlay ) {
10110         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10111     }
10112 }
10113
10114
10115 // [HGM] save: calculate checksum of game to make games easily identifiable
10116 int StringCheckSum(char *s)
10117 {
10118         int i = 0;
10119         if(s==NULL) return 0;
10120         while(*s) i = i*259 + *s++;
10121         return i;
10122 }
10123
10124 int GameCheckSum()
10125 {
10126         int i, sum=0;
10127         for(i=backwardMostMove; i<forwardMostMove; i++) {
10128                 sum += pvInfoList[i].depth;
10129                 sum += StringCheckSum(parseList[i]);
10130                 sum += StringCheckSum(commentList[i]);
10131                 sum *= 261;
10132         }
10133         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10134         return sum + StringCheckSum(commentList[i]);
10135 } // end of save patch
10136
10137 void
10138 GameEnds(result, resultDetails, whosays)
10139      ChessMove result;
10140      char *resultDetails;
10141      int whosays;
10142 {
10143     GameMode nextGameMode;
10144     int isIcsGame;
10145     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10146
10147     if(endingGame) return; /* [HGM] crash: forbid recursion */
10148     endingGame = 1;
10149     if(twoBoards) { // [HGM] dual: switch back to one board
10150         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10151         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10152     }
10153     if (appData.debugMode) {
10154       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10155               result, resultDetails ? resultDetails : "(null)", whosays);
10156     }
10157
10158     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10159
10160     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10161         /* If we are playing on ICS, the server decides when the
10162            game is over, but the engine can offer to draw, claim
10163            a draw, or resign.
10164          */
10165 #if ZIPPY
10166         if (appData.zippyPlay && first.initDone) {
10167             if (result == GameIsDrawn) {
10168                 /* In case draw still needs to be claimed */
10169                 SendToICS(ics_prefix);
10170                 SendToICS("draw\n");
10171             } else if (StrCaseStr(resultDetails, "resign")) {
10172                 SendToICS(ics_prefix);
10173                 SendToICS("resign\n");
10174             }
10175         }
10176 #endif
10177         endingGame = 0; /* [HGM] crash */
10178         return;
10179     }
10180
10181     /* If we're loading the game from a file, stop */
10182     if (whosays == GE_FILE) {
10183       (void) StopLoadGameTimer();
10184       gameFileFP = NULL;
10185     }
10186
10187     /* Cancel draw offers */
10188     first.offeredDraw = second.offeredDraw = 0;
10189
10190     /* If this is an ICS game, only ICS can really say it's done;
10191        if not, anyone can. */
10192     isIcsGame = (gameMode == IcsPlayingWhite ||
10193                  gameMode == IcsPlayingBlack ||
10194                  gameMode == IcsObserving    ||
10195                  gameMode == IcsExamining);
10196
10197     if (!isIcsGame || whosays == GE_ICS) {
10198         /* OK -- not an ICS game, or ICS said it was done */
10199         StopClocks();
10200         if (!isIcsGame && !appData.noChessProgram)
10201           SetUserThinkingEnables();
10202
10203         /* [HGM] if a machine claims the game end we verify this claim */
10204         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10205             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10206                 char claimer;
10207                 ChessMove trueResult = (ChessMove) -1;
10208
10209                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10210                                             first.twoMachinesColor[0] :
10211                                             second.twoMachinesColor[0] ;
10212
10213                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10214                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10215                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10216                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10217                 } else
10218                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10219                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10220                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10221                 } else
10222                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10223                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10224                 }
10225
10226                 // now verify win claims, but not in drop games, as we don't understand those yet
10227                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10228                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10229                     (result == WhiteWins && claimer == 'w' ||
10230                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10231                       if (appData.debugMode) {
10232                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10233                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10234                       }
10235                       if(result != trueResult) {
10236                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10237                               result = claimer == 'w' ? BlackWins : WhiteWins;
10238                               resultDetails = buf;
10239                       }
10240                 } else
10241                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10242                     && (forwardMostMove <= backwardMostMove ||
10243                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10244                         (claimer=='b')==(forwardMostMove&1))
10245                                                                                   ) {
10246                       /* [HGM] verify: draws that were not flagged are false claims */
10247                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10248                       result = claimer == 'w' ? BlackWins : WhiteWins;
10249                       resultDetails = buf;
10250                 }
10251                 /* (Claiming a loss is accepted no questions asked!) */
10252             }
10253             /* [HGM] bare: don't allow bare King to win */
10254             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10255                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10256                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10257                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10258                && result != GameIsDrawn)
10259             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10260                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10261                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10262                         if(p >= 0 && p <= (int)WhiteKing) k++;
10263                 }
10264                 if (appData.debugMode) {
10265                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10266                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10267                 }
10268                 if(k <= 1) {
10269                         result = GameIsDrawn;
10270                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10271                         resultDetails = buf;
10272                 }
10273             }
10274         }
10275
10276
10277         if(serverMoves != NULL && !loadFlag) { char c = '=';
10278             if(result==WhiteWins) c = '+';
10279             if(result==BlackWins) c = '-';
10280             if(resultDetails != NULL)
10281                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10282         }
10283         if (resultDetails != NULL) {
10284             gameInfo.result = result;
10285             gameInfo.resultDetails = StrSave(resultDetails);
10286
10287             /* display last move only if game was not loaded from file */
10288             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10289                 DisplayMove(currentMove - 1);
10290
10291             if (forwardMostMove != 0) {
10292                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10293                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10294                                                                 ) {
10295                     if (*appData.saveGameFile != NULLCHAR) {
10296                         SaveGameToFile(appData.saveGameFile, TRUE);
10297                     } else if (appData.autoSaveGames) {
10298                         AutoSaveGame();
10299                     }
10300                     if (*appData.savePositionFile != NULLCHAR) {
10301                         SavePositionToFile(appData.savePositionFile);
10302                     }
10303                 }
10304             }
10305
10306             /* Tell program how game ended in case it is learning */
10307             /* [HGM] Moved this to after saving the PGN, just in case */
10308             /* engine died and we got here through time loss. In that */
10309             /* case we will get a fatal error writing the pipe, which */
10310             /* would otherwise lose us the PGN.                       */
10311             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10312             /* output during GameEnds should never be fatal anymore   */
10313             if (gameMode == MachinePlaysWhite ||
10314                 gameMode == MachinePlaysBlack ||
10315                 gameMode == TwoMachinesPlay ||
10316                 gameMode == IcsPlayingWhite ||
10317                 gameMode == IcsPlayingBlack ||
10318                 gameMode == BeginningOfGame) {
10319                 char buf[MSG_SIZ];
10320                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10321                         resultDetails);
10322                 if (first.pr != NoProc) {
10323                     SendToProgram(buf, &first);
10324                 }
10325                 if (second.pr != NoProc &&
10326                     gameMode == TwoMachinesPlay) {
10327                     SendToProgram(buf, &second);
10328                 }
10329             }
10330         }
10331
10332         if (appData.icsActive) {
10333             if (appData.quietPlay &&
10334                 (gameMode == IcsPlayingWhite ||
10335                  gameMode == IcsPlayingBlack)) {
10336                 SendToICS(ics_prefix);
10337                 SendToICS("set shout 1\n");
10338             }
10339             nextGameMode = IcsIdle;
10340             ics_user_moved = FALSE;
10341             /* clean up premove.  It's ugly when the game has ended and the
10342              * premove highlights are still on the board.
10343              */
10344             if (gotPremove) {
10345               gotPremove = FALSE;
10346               ClearPremoveHighlights();
10347               DrawPosition(FALSE, boards[currentMove]);
10348             }
10349             if (whosays == GE_ICS) {
10350                 switch (result) {
10351                 case WhiteWins:
10352                     if (gameMode == IcsPlayingWhite)
10353                         PlayIcsWinSound();
10354                     else if(gameMode == IcsPlayingBlack)
10355                         PlayIcsLossSound();
10356                     break;
10357                 case BlackWins:
10358                     if (gameMode == IcsPlayingBlack)
10359                         PlayIcsWinSound();
10360                     else if(gameMode == IcsPlayingWhite)
10361                         PlayIcsLossSound();
10362                     break;
10363                 case GameIsDrawn:
10364                     PlayIcsDrawSound();
10365                     break;
10366                 default:
10367                     PlayIcsUnfinishedSound();
10368                 }
10369             }
10370         } else if (gameMode == EditGame ||
10371                    gameMode == PlayFromGameFile ||
10372                    gameMode == AnalyzeMode ||
10373                    gameMode == AnalyzeFile) {
10374             nextGameMode = gameMode;
10375         } else {
10376             nextGameMode = EndOfGame;
10377         }
10378         pausing = FALSE;
10379         ModeHighlight();
10380     } else {
10381         nextGameMode = gameMode;
10382     }
10383
10384     if (appData.noChessProgram) {
10385         gameMode = nextGameMode;
10386         ModeHighlight();
10387         endingGame = 0; /* [HGM] crash */
10388         return;
10389     }
10390
10391     if (first.reuse) {
10392         /* Put first chess program into idle state */
10393         if (first.pr != NoProc &&
10394             (gameMode == MachinePlaysWhite ||
10395              gameMode == MachinePlaysBlack ||
10396              gameMode == TwoMachinesPlay ||
10397              gameMode == IcsPlayingWhite ||
10398              gameMode == IcsPlayingBlack ||
10399              gameMode == BeginningOfGame)) {
10400             SendToProgram("force\n", &first);
10401             if (first.usePing) {
10402               char buf[MSG_SIZ];
10403               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10404               SendToProgram(buf, &first);
10405             }
10406         }
10407     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10408         /* Kill off first chess program */
10409         if (first.isr != NULL)
10410           RemoveInputSource(first.isr);
10411         first.isr = NULL;
10412
10413         if (first.pr != NoProc) {
10414             ExitAnalyzeMode();
10415             DoSleep( appData.delayBeforeQuit );
10416             SendToProgram("quit\n", &first);
10417             DoSleep( appData.delayAfterQuit );
10418             DestroyChildProcess(first.pr, first.useSigterm);
10419         }
10420         first.pr = NoProc;
10421     }
10422     if (second.reuse) {
10423         /* Put second chess program into idle state */
10424         if (second.pr != NoProc &&
10425             gameMode == TwoMachinesPlay) {
10426             SendToProgram("force\n", &second);
10427             if (second.usePing) {
10428               char buf[MSG_SIZ];
10429               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10430               SendToProgram(buf, &second);
10431             }
10432         }
10433     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10434         /* Kill off second chess program */
10435         if (second.isr != NULL)
10436           RemoveInputSource(second.isr);
10437         second.isr = NULL;
10438
10439         if (second.pr != NoProc) {
10440             DoSleep( appData.delayBeforeQuit );
10441             SendToProgram("quit\n", &second);
10442             DoSleep( appData.delayAfterQuit );
10443             DestroyChildProcess(second.pr, second.useSigterm);
10444         }
10445         second.pr = NoProc;
10446     }
10447
10448     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10449         char resChar = '=';
10450         switch (result) {
10451         case WhiteWins:
10452           resChar = '+';
10453           if (first.twoMachinesColor[0] == 'w') {
10454             first.matchWins++;
10455           } else {
10456             second.matchWins++;
10457           }
10458           break;
10459         case BlackWins:
10460           resChar = '-';
10461           if (first.twoMachinesColor[0] == 'b') {
10462             first.matchWins++;
10463           } else {
10464             second.matchWins++;
10465           }
10466           break;
10467         case GameUnfinished:
10468           resChar = ' ';
10469         default:
10470           break;
10471         }
10472
10473         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10474         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10475             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10476             ReserveGame(nextGame, resChar); // sets nextGame
10477             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10478             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10479         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10480
10481         if (nextGame <= appData.matchGames && !abortMatch) {
10482             gameMode = nextGameMode;
10483             matchGame = nextGame; // this will be overruled in tourney mode!
10484             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10485             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10486             endingGame = 0; /* [HGM] crash */
10487             return;
10488         } else {
10489             gameMode = nextGameMode;
10490             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10491                      first.tidy, second.tidy,
10492                      first.matchWins, second.matchWins,
10493                      appData.matchGames - (first.matchWins + second.matchWins));
10494             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10495             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10496             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10497             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10498                 first.twoMachinesColor = "black\n";
10499                 second.twoMachinesColor = "white\n";
10500             } else {
10501                 first.twoMachinesColor = "white\n";
10502                 second.twoMachinesColor = "black\n";
10503             }
10504         }
10505     }
10506     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10507         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10508       ExitAnalyzeMode();
10509     gameMode = nextGameMode;
10510     ModeHighlight();
10511     endingGame = 0;  /* [HGM] crash */
10512     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10513         if(matchMode == TRUE) { // match through command line: exit with or without popup
10514             if(ranking) {
10515                 ToNrEvent(forwardMostMove);
10516                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10517                 else ExitEvent(0);
10518             } else DisplayFatalError(buf, 0, 0);
10519         } else { // match through menu; just stop, with or without popup
10520             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10521             ModeHighlight();
10522             if(ranking){
10523                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10524             } else DisplayNote(buf);
10525       }
10526       if(ranking) free(ranking);
10527     }
10528 }
10529
10530 /* Assumes program was just initialized (initString sent).
10531    Leaves program in force mode. */
10532 void
10533 FeedMovesToProgram(cps, upto)
10534      ChessProgramState *cps;
10535      int upto;
10536 {
10537     int i;
10538
10539     if (appData.debugMode)
10540       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10541               startedFromSetupPosition ? "position and " : "",
10542               backwardMostMove, upto, cps->which);
10543     if(currentlyInitializedVariant != gameInfo.variant) {
10544       char buf[MSG_SIZ];
10545         // [HGM] variantswitch: make engine aware of new variant
10546         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10547                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10548         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10549         SendToProgram(buf, cps);
10550         currentlyInitializedVariant = gameInfo.variant;
10551     }
10552     SendToProgram("force\n", cps);
10553     if (startedFromSetupPosition) {
10554         SendBoard(cps, backwardMostMove);
10555     if (appData.debugMode) {
10556         fprintf(debugFP, "feedMoves\n");
10557     }
10558     }
10559     for (i = backwardMostMove; i < upto; i++) {
10560         SendMoveToProgram(i, cps);
10561     }
10562 }
10563
10564
10565 int
10566 ResurrectChessProgram()
10567 {
10568      /* The chess program may have exited.
10569         If so, restart it and feed it all the moves made so far. */
10570     static int doInit = 0;
10571
10572     if (appData.noChessProgram) return 1;
10573
10574     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10575         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10576         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10577         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10578     } else {
10579         if (first.pr != NoProc) return 1;
10580         StartChessProgram(&first);
10581     }
10582     InitChessProgram(&first, FALSE);
10583     FeedMovesToProgram(&first, currentMove);
10584
10585     if (!first.sendTime) {
10586         /* can't tell gnuchess what its clock should read,
10587            so we bow to its notion. */
10588         ResetClocks();
10589         timeRemaining[0][currentMove] = whiteTimeRemaining;
10590         timeRemaining[1][currentMove] = blackTimeRemaining;
10591     }
10592
10593     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10594                 appData.icsEngineAnalyze) && first.analysisSupport) {
10595       SendToProgram("analyze\n", &first);
10596       first.analyzing = TRUE;
10597     }
10598     return 1;
10599 }
10600
10601 /*
10602  * Button procedures
10603  */
10604 void
10605 Reset(redraw, init)
10606      int redraw, init;
10607 {
10608     int i;
10609
10610     if (appData.debugMode) {
10611         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10612                 redraw, init, gameMode);
10613     }
10614     CleanupTail(); // [HGM] vari: delete any stored variations
10615     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10616     pausing = pauseExamInvalid = FALSE;
10617     startedFromSetupPosition = blackPlaysFirst = FALSE;
10618     firstMove = TRUE;
10619     whiteFlag = blackFlag = FALSE;
10620     userOfferedDraw = FALSE;
10621     hintRequested = bookRequested = FALSE;
10622     first.maybeThinking = FALSE;
10623     second.maybeThinking = FALSE;
10624     first.bookSuspend = FALSE; // [HGM] book
10625     second.bookSuspend = FALSE;
10626     thinkOutput[0] = NULLCHAR;
10627     lastHint[0] = NULLCHAR;
10628     ClearGameInfo(&gameInfo);
10629     gameInfo.variant = StringToVariant(appData.variant);
10630     ics_user_moved = ics_clock_paused = FALSE;
10631     ics_getting_history = H_FALSE;
10632     ics_gamenum = -1;
10633     white_holding[0] = black_holding[0] = NULLCHAR;
10634     ClearProgramStats();
10635     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10636
10637     ResetFrontEnd();
10638     ClearHighlights();
10639     flipView = appData.flipView;
10640     ClearPremoveHighlights();
10641     gotPremove = FALSE;
10642     alarmSounded = FALSE;
10643
10644     GameEnds(EndOfFile, NULL, GE_PLAYER);
10645     if(appData.serverMovesName != NULL) {
10646         /* [HGM] prepare to make moves file for broadcasting */
10647         clock_t t = clock();
10648         if(serverMoves != NULL) fclose(serverMoves);
10649         serverMoves = fopen(appData.serverMovesName, "r");
10650         if(serverMoves != NULL) {
10651             fclose(serverMoves);
10652             /* delay 15 sec before overwriting, so all clients can see end */
10653             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10654         }
10655         serverMoves = fopen(appData.serverMovesName, "w");
10656     }
10657
10658     ExitAnalyzeMode();
10659     gameMode = BeginningOfGame;
10660     ModeHighlight();
10661     if(appData.icsActive) gameInfo.variant = VariantNormal;
10662     currentMove = forwardMostMove = backwardMostMove = 0;
10663     MarkTargetSquares(1);
10664     InitPosition(redraw);
10665     for (i = 0; i < MAX_MOVES; i++) {
10666         if (commentList[i] != NULL) {
10667             free(commentList[i]);
10668             commentList[i] = NULL;
10669         }
10670     }
10671     ResetClocks();
10672     timeRemaining[0][0] = whiteTimeRemaining;
10673     timeRemaining[1][0] = blackTimeRemaining;
10674
10675     if (first.pr == NoProc) {
10676         StartChessProgram(&first);
10677     }
10678     if (init) {
10679             InitChessProgram(&first, startedFromSetupPosition);
10680     }
10681     DisplayTitle("");
10682     DisplayMessage("", "");
10683     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10684     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10685 }
10686
10687 void
10688 AutoPlayGameLoop()
10689 {
10690     for (;;) {
10691         if (!AutoPlayOneMove())
10692           return;
10693         if (matchMode || appData.timeDelay == 0)
10694           continue;
10695         if (appData.timeDelay < 0)
10696           return;
10697         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10698         break;
10699     }
10700 }
10701
10702
10703 int
10704 AutoPlayOneMove()
10705 {
10706     int fromX, fromY, toX, toY;
10707
10708     if (appData.debugMode) {
10709       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10710     }
10711
10712     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10713       return FALSE;
10714
10715     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10716       pvInfoList[currentMove].depth = programStats.depth;
10717       pvInfoList[currentMove].score = programStats.score;
10718       pvInfoList[currentMove].time  = 0;
10719       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10720     }
10721
10722     if (currentMove >= forwardMostMove) {
10723       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10724 //      gameMode = EndOfGame;
10725 //      ModeHighlight();
10726
10727       /* [AS] Clear current move marker at the end of a game */
10728       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10729
10730       return FALSE;
10731     }
10732
10733     toX = moveList[currentMove][2] - AAA;
10734     toY = moveList[currentMove][3] - ONE;
10735
10736     if (moveList[currentMove][1] == '@') {
10737         if (appData.highlightLastMove) {
10738             SetHighlights(-1, -1, toX, toY);
10739         }
10740     } else {
10741         fromX = moveList[currentMove][0] - AAA;
10742         fromY = moveList[currentMove][1] - ONE;
10743
10744         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10745
10746         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10747
10748         if (appData.highlightLastMove) {
10749             SetHighlights(fromX, fromY, toX, toY);
10750         }
10751     }
10752     DisplayMove(currentMove);
10753     SendMoveToProgram(currentMove++, &first);
10754     DisplayBothClocks();
10755     DrawPosition(FALSE, boards[currentMove]);
10756     // [HGM] PV info: always display, routine tests if empty
10757     DisplayComment(currentMove - 1, commentList[currentMove]);
10758     return TRUE;
10759 }
10760
10761
10762 int
10763 LoadGameOneMove(readAhead)
10764      ChessMove readAhead;
10765 {
10766     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10767     char promoChar = NULLCHAR;
10768     ChessMove moveType;
10769     char move[MSG_SIZ];
10770     char *p, *q;
10771
10772     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10773         gameMode != AnalyzeMode && gameMode != Training) {
10774         gameFileFP = NULL;
10775         return FALSE;
10776     }
10777
10778     yyboardindex = forwardMostMove;
10779     if (readAhead != EndOfFile) {
10780       moveType = readAhead;
10781     } else {
10782       if (gameFileFP == NULL)
10783           return FALSE;
10784       moveType = (ChessMove) Myylex();
10785     }
10786
10787     done = FALSE;
10788     switch (moveType) {
10789       case Comment:
10790         if (appData.debugMode)
10791           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10792         p = yy_text;
10793
10794         /* append the comment but don't display it */
10795         AppendComment(currentMove, p, FALSE);
10796         return TRUE;
10797
10798       case WhiteCapturesEnPassant:
10799       case BlackCapturesEnPassant:
10800       case WhitePromotion:
10801       case BlackPromotion:
10802       case WhiteNonPromotion:
10803       case BlackNonPromotion:
10804       case NormalMove:
10805       case WhiteKingSideCastle:
10806       case WhiteQueenSideCastle:
10807       case BlackKingSideCastle:
10808       case BlackQueenSideCastle:
10809       case WhiteKingSideCastleWild:
10810       case WhiteQueenSideCastleWild:
10811       case BlackKingSideCastleWild:
10812       case BlackQueenSideCastleWild:
10813       /* PUSH Fabien */
10814       case WhiteHSideCastleFR:
10815       case WhiteASideCastleFR:
10816       case BlackHSideCastleFR:
10817       case BlackASideCastleFR:
10818       /* POP Fabien */
10819         if (appData.debugMode)
10820           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10821         fromX = currentMoveString[0] - AAA;
10822         fromY = currentMoveString[1] - ONE;
10823         toX = currentMoveString[2] - AAA;
10824         toY = currentMoveString[3] - ONE;
10825         promoChar = currentMoveString[4];
10826         break;
10827
10828       case WhiteDrop:
10829       case BlackDrop:
10830         if (appData.debugMode)
10831           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10832         fromX = moveType == WhiteDrop ?
10833           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10834         (int) CharToPiece(ToLower(currentMoveString[0]));
10835         fromY = DROP_RANK;
10836         toX = currentMoveString[2] - AAA;
10837         toY = currentMoveString[3] - ONE;
10838         break;
10839
10840       case WhiteWins:
10841       case BlackWins:
10842       case GameIsDrawn:
10843       case GameUnfinished:
10844         if (appData.debugMode)
10845           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10846         p = strchr(yy_text, '{');
10847         if (p == NULL) p = strchr(yy_text, '(');
10848         if (p == NULL) {
10849             p = yy_text;
10850             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10851         } else {
10852             q = strchr(p, *p == '{' ? '}' : ')');
10853             if (q != NULL) *q = NULLCHAR;
10854             p++;
10855         }
10856         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10857         GameEnds(moveType, p, GE_FILE);
10858         done = TRUE;
10859         if (cmailMsgLoaded) {
10860             ClearHighlights();
10861             flipView = WhiteOnMove(currentMove);
10862             if (moveType == GameUnfinished) flipView = !flipView;
10863             if (appData.debugMode)
10864               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10865         }
10866         break;
10867
10868       case EndOfFile:
10869         if (appData.debugMode)
10870           fprintf(debugFP, "Parser hit end of file\n");
10871         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10872           case MT_NONE:
10873           case MT_CHECK:
10874             break;
10875           case MT_CHECKMATE:
10876           case MT_STAINMATE:
10877             if (WhiteOnMove(currentMove)) {
10878                 GameEnds(BlackWins, "Black mates", GE_FILE);
10879             } else {
10880                 GameEnds(WhiteWins, "White mates", GE_FILE);
10881             }
10882             break;
10883           case MT_STALEMATE:
10884             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10885             break;
10886         }
10887         done = TRUE;
10888         break;
10889
10890       case MoveNumberOne:
10891         if (lastLoadGameStart == GNUChessGame) {
10892             /* GNUChessGames have numbers, but they aren't move numbers */
10893             if (appData.debugMode)
10894               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10895                       yy_text, (int) moveType);
10896             return LoadGameOneMove(EndOfFile); /* tail recursion */
10897         }
10898         /* else fall thru */
10899
10900       case XBoardGame:
10901       case GNUChessGame:
10902       case PGNTag:
10903         /* Reached start of next game in file */
10904         if (appData.debugMode)
10905           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10906         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10907           case MT_NONE:
10908           case MT_CHECK:
10909             break;
10910           case MT_CHECKMATE:
10911           case MT_STAINMATE:
10912             if (WhiteOnMove(currentMove)) {
10913                 GameEnds(BlackWins, "Black mates", GE_FILE);
10914             } else {
10915                 GameEnds(WhiteWins, "White mates", GE_FILE);
10916             }
10917             break;
10918           case MT_STALEMATE:
10919             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10920             break;
10921         }
10922         done = TRUE;
10923         break;
10924
10925       case PositionDiagram:     /* should not happen; ignore */
10926       case ElapsedTime:         /* ignore */
10927       case NAG:                 /* ignore */
10928         if (appData.debugMode)
10929           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10930                   yy_text, (int) moveType);
10931         return LoadGameOneMove(EndOfFile); /* tail recursion */
10932
10933       case IllegalMove:
10934         if (appData.testLegality) {
10935             if (appData.debugMode)
10936               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10937             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10938                     (forwardMostMove / 2) + 1,
10939                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10940             DisplayError(move, 0);
10941             done = TRUE;
10942         } else {
10943             if (appData.debugMode)
10944               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10945                       yy_text, currentMoveString);
10946             fromX = currentMoveString[0] - AAA;
10947             fromY = currentMoveString[1] - ONE;
10948             toX = currentMoveString[2] - AAA;
10949             toY = currentMoveString[3] - ONE;
10950             promoChar = currentMoveString[4];
10951         }
10952         break;
10953
10954       case AmbiguousMove:
10955         if (appData.debugMode)
10956           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10957         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10958                 (forwardMostMove / 2) + 1,
10959                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10960         DisplayError(move, 0);
10961         done = TRUE;
10962         break;
10963
10964       default:
10965       case ImpossibleMove:
10966         if (appData.debugMode)
10967           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10968         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10969                 (forwardMostMove / 2) + 1,
10970                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10971         DisplayError(move, 0);
10972         done = TRUE;
10973         break;
10974     }
10975
10976     if (done) {
10977         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10978             DrawPosition(FALSE, boards[currentMove]);
10979             DisplayBothClocks();
10980             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10981               DisplayComment(currentMove - 1, commentList[currentMove]);
10982         }
10983         (void) StopLoadGameTimer();
10984         gameFileFP = NULL;
10985         cmailOldMove = forwardMostMove;
10986         return FALSE;
10987     } else {
10988         /* currentMoveString is set as a side-effect of yylex */
10989
10990         thinkOutput[0] = NULLCHAR;
10991         MakeMove(fromX, fromY, toX, toY, promoChar);
10992         currentMove = forwardMostMove;
10993         return TRUE;
10994     }
10995 }
10996
10997 /* Load the nth game from the given file */
10998 int
10999 LoadGameFromFile(filename, n, title, useList)
11000      char *filename;
11001      int n;
11002      char *title;
11003      /*Boolean*/ int useList;
11004 {
11005     FILE *f;
11006     char buf[MSG_SIZ];
11007
11008     if (strcmp(filename, "-") == 0) {
11009         f = stdin;
11010         title = "stdin";
11011     } else {
11012         f = fopen(filename, "rb");
11013         if (f == NULL) {
11014           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11015             DisplayError(buf, errno);
11016             return FALSE;
11017         }
11018     }
11019     if (fseek(f, 0, 0) == -1) {
11020         /* f is not seekable; probably a pipe */
11021         useList = FALSE;
11022     }
11023     if (useList && n == 0) {
11024         int error = GameListBuild(f);
11025         if (error) {
11026             DisplayError(_("Cannot build game list"), error);
11027         } else if (!ListEmpty(&gameList) &&
11028                    ((ListGame *) gameList.tailPred)->number > 1) {
11029             GameListPopUp(f, title);
11030             return TRUE;
11031         }
11032         GameListDestroy();
11033         n = 1;
11034     }
11035     if (n == 0) n = 1;
11036     return LoadGame(f, n, title, FALSE);
11037 }
11038
11039
11040 void
11041 MakeRegisteredMove()
11042 {
11043     int fromX, fromY, toX, toY;
11044     char promoChar;
11045     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11046         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11047           case CMAIL_MOVE:
11048           case CMAIL_DRAW:
11049             if (appData.debugMode)
11050               fprintf(debugFP, "Restoring %s for game %d\n",
11051                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11052
11053             thinkOutput[0] = NULLCHAR;
11054             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11055             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11056             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11057             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11058             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11059             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11060             MakeMove(fromX, fromY, toX, toY, promoChar);
11061             ShowMove(fromX, fromY, toX, toY);
11062
11063             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11064               case MT_NONE:
11065               case MT_CHECK:
11066                 break;
11067
11068               case MT_CHECKMATE:
11069               case MT_STAINMATE:
11070                 if (WhiteOnMove(currentMove)) {
11071                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11072                 } else {
11073                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11074                 }
11075                 break;
11076
11077               case MT_STALEMATE:
11078                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11079                 break;
11080             }
11081
11082             break;
11083
11084           case CMAIL_RESIGN:
11085             if (WhiteOnMove(currentMove)) {
11086                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11087             } else {
11088                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11089             }
11090             break;
11091
11092           case CMAIL_ACCEPT:
11093             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11094             break;
11095
11096           default:
11097             break;
11098         }
11099     }
11100
11101     return;
11102 }
11103
11104 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11105 int
11106 CmailLoadGame(f, gameNumber, title, useList)
11107      FILE *f;
11108      int gameNumber;
11109      char *title;
11110      int useList;
11111 {
11112     int retVal;
11113
11114     if (gameNumber > nCmailGames) {
11115         DisplayError(_("No more games in this message"), 0);
11116         return FALSE;
11117     }
11118     if (f == lastLoadGameFP) {
11119         int offset = gameNumber - lastLoadGameNumber;
11120         if (offset == 0) {
11121             cmailMsg[0] = NULLCHAR;
11122             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11123                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11124                 nCmailMovesRegistered--;
11125             }
11126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11127             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11128                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11129             }
11130         } else {
11131             if (! RegisterMove()) return FALSE;
11132         }
11133     }
11134
11135     retVal = LoadGame(f, gameNumber, title, useList);
11136
11137     /* Make move registered during previous look at this game, if any */
11138     MakeRegisteredMove();
11139
11140     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11141         commentList[currentMove]
11142           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11143         DisplayComment(currentMove - 1, commentList[currentMove]);
11144     }
11145
11146     return retVal;
11147 }
11148
11149 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11150 int
11151 ReloadGame(offset)
11152      int offset;
11153 {
11154     int gameNumber = lastLoadGameNumber + offset;
11155     if (lastLoadGameFP == NULL) {
11156         DisplayError(_("No game has been loaded yet"), 0);
11157         return FALSE;
11158     }
11159     if (gameNumber <= 0) {
11160         DisplayError(_("Can't back up any further"), 0);
11161         return FALSE;
11162     }
11163     if (cmailMsgLoaded) {
11164         return CmailLoadGame(lastLoadGameFP, gameNumber,
11165                              lastLoadGameTitle, lastLoadGameUseList);
11166     } else {
11167         return LoadGame(lastLoadGameFP, gameNumber,
11168                         lastLoadGameTitle, lastLoadGameUseList);
11169     }
11170 }
11171
11172 int keys[EmptySquare+1];
11173
11174 int
11175 PositionMatches(Board b1, Board b2)
11176 {
11177     int r, f, sum=0;
11178     switch(appData.searchMode) {
11179         case 1: return CompareWithRights(b1, b2);
11180         case 2:
11181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11182                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11183             }
11184             return TRUE;
11185         case 3:
11186             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11187               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11188                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11189             }
11190             return sum==0;
11191         case 4:
11192             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11193                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11194             }
11195             return sum==0;
11196     }
11197     return TRUE;
11198 }
11199
11200 #define Q_PROMO  4
11201 #define Q_EP     3
11202 #define Q_BCASTL 2
11203 #define Q_WCASTL 1
11204
11205 int pieceList[256], quickBoard[256];
11206 ChessSquare pieceType[256] = { EmptySquare };
11207 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11208 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11209 int soughtTotal, turn;
11210 Boolean epOK, flipSearch;
11211
11212 typedef struct {
11213     unsigned char piece, to;
11214 } Move;
11215
11216 #define DSIZE (250000)
11217
11218 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11219 Move *moveDatabase = initialSpace;
11220 unsigned int movePtr, dataSize = DSIZE;
11221
11222 int MakePieceList(Board board, int *counts)
11223 {
11224     int r, f, n=Q_PROMO, total=0;
11225     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11226     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11227         int sq = f + (r<<4);
11228         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11229             quickBoard[sq] = ++n;
11230             pieceList[n] = sq;
11231             pieceType[n] = board[r][f];
11232             counts[board[r][f]]++;
11233             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11234             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11235             total++;
11236         }
11237     }
11238     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11239     return total;
11240 }
11241
11242 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11243 {
11244     int sq = fromX + (fromY<<4);
11245     int piece = quickBoard[sq];
11246     quickBoard[sq] = 0;
11247     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11248     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11249         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11250         moveDatabase[movePtr++].piece = Q_WCASTL;
11251         quickBoard[sq] = piece;
11252         piece = quickBoard[from]; quickBoard[from] = 0;
11253         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11254     } else
11255     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11256         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11257         moveDatabase[movePtr++].piece = Q_BCASTL;
11258         quickBoard[sq] = piece;
11259         piece = quickBoard[from]; quickBoard[from] = 0;
11260         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11261     } else
11262     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11263         quickBoard[(fromY<<4)+toX] = 0;
11264         moveDatabase[movePtr].piece = Q_EP;
11265         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11266         moveDatabase[movePtr].to = sq;
11267     } else
11268     if(promoPiece != pieceType[piece]) {
11269         moveDatabase[movePtr++].piece = Q_PROMO;
11270         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11271     }
11272     moveDatabase[movePtr].piece = piece;
11273     quickBoard[sq] = piece;
11274     movePtr++;
11275 }
11276
11277 int PackGame(Board board)
11278 {
11279     Move *newSpace = NULL;
11280     moveDatabase[movePtr].piece = 0; // terminate previous game
11281     if(movePtr > dataSize) {
11282         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11283         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11284         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11285         if(newSpace) {
11286             int i;
11287             Move *p = moveDatabase, *q = newSpace;
11288             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11289             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11290             moveDatabase = newSpace;
11291         } else { // calloc failed, we must be out of memory. Too bad...
11292             dataSize = 0; // prevent calloc events for all subsequent games
11293             return 0;     // and signal this one isn't cached
11294         }
11295     }
11296     movePtr++;
11297     MakePieceList(board, counts);
11298     return movePtr;
11299 }
11300
11301 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11302 {   // compare according to search mode
11303     int r, f;
11304     switch(appData.searchMode)
11305     {
11306       case 1: // exact position match
11307         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11308         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11309             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11310         }
11311         break;
11312       case 2: // can have extra material on empty squares
11313         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11314             if(board[r][f] == EmptySquare) continue;
11315             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11316         }
11317         break;
11318       case 3: // material with exact Pawn structure
11319         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11320             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11321             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11322         } // fall through to material comparison
11323       case 4: // exact material
11324         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11325         break;
11326       case 6: // material range with given imbalance
11327         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11328         // fall through to range comparison
11329       case 5: // material range
11330         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11331     }
11332     return TRUE;
11333 }
11334
11335 int QuickScan(Board board, Move *move)
11336 {   // reconstruct game,and compare all positions in it
11337     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11338     do {
11339         int piece = move->piece;
11340         int to = move->to, from = pieceList[piece];
11341         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11342           if(!piece) return -1;
11343           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11344             piece = (++move)->piece;
11345             from = pieceList[piece];
11346             counts[pieceType[piece]]--;
11347             pieceType[piece] = (ChessSquare) move->to;
11348             counts[move->to]++;
11349           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11350             counts[pieceType[quickBoard[to]]]--;
11351             quickBoard[to] = 0; total--;
11352             move++;
11353             continue;
11354           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11355             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11356             from  = pieceList[piece]; // so this must be King
11357             quickBoard[from] = 0;
11358             quickBoard[to] = piece;
11359             pieceList[piece] = to;
11360             move++;
11361             continue;
11362           }
11363         }
11364         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11365         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11366         quickBoard[from] = 0;
11367         quickBoard[to] = piece;
11368         pieceList[piece] = to;
11369         cnt++; turn ^= 3;
11370         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11371            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11372            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11373                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11374           ) {
11375             static int lastCounts[EmptySquare+1];
11376             int i;
11377             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11378             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11379         } else stretch = 0;
11380         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11381         move++;
11382     } while(1);
11383 }
11384
11385 void InitSearch()
11386 {
11387     int r, f;
11388     flipSearch = FALSE;
11389     CopyBoard(soughtBoard, boards[currentMove]);
11390     soughtTotal = MakePieceList(soughtBoard, maxSought);
11391     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11392     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11393     CopyBoard(reverseBoard, boards[currentMove]);
11394     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11395         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11396         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11397         reverseBoard[r][f] = piece;
11398     }
11399     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11400     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11401     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11402                  || (boards[currentMove][CASTLING][2] == NoRights || 
11403                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11404                  && (boards[currentMove][CASTLING][5] == NoRights || 
11405                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11406       ) {
11407         flipSearch = TRUE;
11408         CopyBoard(flipBoard, soughtBoard);
11409         CopyBoard(rotateBoard, reverseBoard);
11410         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11411             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11412             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11413         }
11414     }
11415     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11416     if(appData.searchMode >= 5) {
11417         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11418         MakePieceList(soughtBoard, minSought);
11419         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11420     }
11421     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11422         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11423 }
11424
11425 GameInfo dummyInfo;
11426
11427 int GameContainsPosition(FILE *f, ListGame *lg)
11428 {
11429     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11430     int fromX, fromY, toX, toY;
11431     char promoChar;
11432     static int initDone=FALSE;
11433
11434     // weed out games based on numerical tag comparison
11435     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11436     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11437     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11438     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11439     if(!initDone) {
11440         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11441         initDone = TRUE;
11442     }
11443     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11444     else CopyBoard(boards[scratch], initialPosition); // default start position
11445     if(lg->moves) {
11446         turn = btm + 1;
11447         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11448         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11449     }
11450     if(btm) plyNr++;
11451     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11452     fseek(f, lg->offset, 0);
11453     yynewfile(f);
11454     while(1) {
11455         yyboardindex = scratch;
11456         quickFlag = plyNr+1;
11457         next = Myylex();
11458         quickFlag = 0;
11459         switch(next) {
11460             case PGNTag:
11461                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11462             default:
11463                 continue;
11464
11465             case XBoardGame:
11466             case GNUChessGame:
11467                 if(plyNr) return -1; // after we have seen moves, this is for new game
11468               continue;
11469
11470             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11471             case ImpossibleMove:
11472             case WhiteWins: // game ends here with these four
11473             case BlackWins:
11474             case GameIsDrawn:
11475             case GameUnfinished:
11476                 return -1;
11477
11478             case IllegalMove:
11479                 if(appData.testLegality) return -1;
11480             case WhiteCapturesEnPassant:
11481             case BlackCapturesEnPassant:
11482             case WhitePromotion:
11483             case BlackPromotion:
11484             case WhiteNonPromotion:
11485             case BlackNonPromotion:
11486             case NormalMove:
11487             case WhiteKingSideCastle:
11488             case WhiteQueenSideCastle:
11489             case BlackKingSideCastle:
11490             case BlackQueenSideCastle:
11491             case WhiteKingSideCastleWild:
11492             case WhiteQueenSideCastleWild:
11493             case BlackKingSideCastleWild:
11494             case BlackQueenSideCastleWild:
11495             case WhiteHSideCastleFR:
11496             case WhiteASideCastleFR:
11497             case BlackHSideCastleFR:
11498             case BlackASideCastleFR:
11499                 fromX = currentMoveString[0] - AAA;
11500                 fromY = currentMoveString[1] - ONE;
11501                 toX = currentMoveString[2] - AAA;
11502                 toY = currentMoveString[3] - ONE;
11503                 promoChar = currentMoveString[4];
11504                 break;
11505             case WhiteDrop:
11506             case BlackDrop:
11507                 fromX = next == WhiteDrop ?
11508                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11509                   (int) CharToPiece(ToLower(currentMoveString[0]));
11510                 fromY = DROP_RANK;
11511                 toX = currentMoveString[2] - AAA;
11512                 toY = currentMoveString[3] - ONE;
11513                 promoChar = 0;
11514                 break;
11515         }
11516         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11517         plyNr++;
11518         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11519         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11520         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11521         if(appData.findMirror) {
11522             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11523             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11524         }
11525     }
11526 }
11527
11528 /* Load the nth game from open file f */
11529 int
11530 LoadGame(f, gameNumber, title, useList)
11531      FILE *f;
11532      int gameNumber;
11533      char *title;
11534      int useList;
11535 {
11536     ChessMove cm;
11537     char buf[MSG_SIZ];
11538     int gn = gameNumber;
11539     ListGame *lg = NULL;
11540     int numPGNTags = 0;
11541     int err, pos = -1;
11542     GameMode oldGameMode;
11543     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11544
11545     if (appData.debugMode)
11546         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11547
11548     if (gameMode == Training )
11549         SetTrainingModeOff();
11550
11551     oldGameMode = gameMode;
11552     if (gameMode != BeginningOfGame) {
11553       Reset(FALSE, TRUE);
11554     }
11555
11556     gameFileFP = f;
11557     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11558         fclose(lastLoadGameFP);
11559     }
11560
11561     if (useList) {
11562         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11563
11564         if (lg) {
11565             fseek(f, lg->offset, 0);
11566             GameListHighlight(gameNumber);
11567             pos = lg->position;
11568             gn = 1;
11569         }
11570         else {
11571             DisplayError(_("Game number out of range"), 0);
11572             return FALSE;
11573         }
11574     } else {
11575         GameListDestroy();
11576         if (fseek(f, 0, 0) == -1) {
11577             if (f == lastLoadGameFP ?
11578                 gameNumber == lastLoadGameNumber + 1 :
11579                 gameNumber == 1) {
11580                 gn = 1;
11581             } else {
11582                 DisplayError(_("Can't seek on game file"), 0);
11583                 return FALSE;
11584             }
11585         }
11586     }
11587     lastLoadGameFP = f;
11588     lastLoadGameNumber = gameNumber;
11589     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11590     lastLoadGameUseList = useList;
11591
11592     yynewfile(f);
11593
11594     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11595       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11596                 lg->gameInfo.black);
11597             DisplayTitle(buf);
11598     } else if (*title != NULLCHAR) {
11599         if (gameNumber > 1) {
11600           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11601             DisplayTitle(buf);
11602         } else {
11603             DisplayTitle(title);
11604         }
11605     }
11606
11607     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11608         gameMode = PlayFromGameFile;
11609         ModeHighlight();
11610     }
11611
11612     currentMove = forwardMostMove = backwardMostMove = 0;
11613     CopyBoard(boards[0], initialPosition);
11614     StopClocks();
11615
11616     /*
11617      * Skip the first gn-1 games in the file.
11618      * Also skip over anything that precedes an identifiable
11619      * start of game marker, to avoid being confused by
11620      * garbage at the start of the file.  Currently
11621      * recognized start of game markers are the move number "1",
11622      * the pattern "gnuchess .* game", the pattern
11623      * "^[#;%] [^ ]* game file", and a PGN tag block.
11624      * A game that starts with one of the latter two patterns
11625      * will also have a move number 1, possibly
11626      * following a position diagram.
11627      * 5-4-02: Let's try being more lenient and allowing a game to
11628      * start with an unnumbered move.  Does that break anything?
11629      */
11630     cm = lastLoadGameStart = EndOfFile;
11631     while (gn > 0) {
11632         yyboardindex = forwardMostMove;
11633         cm = (ChessMove) Myylex();
11634         switch (cm) {
11635           case EndOfFile:
11636             if (cmailMsgLoaded) {
11637                 nCmailGames = CMAIL_MAX_GAMES - gn;
11638             } else {
11639                 Reset(TRUE, TRUE);
11640                 DisplayError(_("Game not found in file"), 0);
11641             }
11642             return FALSE;
11643
11644           case GNUChessGame:
11645           case XBoardGame:
11646             gn--;
11647             lastLoadGameStart = cm;
11648             break;
11649
11650           case MoveNumberOne:
11651             switch (lastLoadGameStart) {
11652               case GNUChessGame:
11653               case XBoardGame:
11654               case PGNTag:
11655                 break;
11656               case MoveNumberOne:
11657               case EndOfFile:
11658                 gn--;           /* count this game */
11659                 lastLoadGameStart = cm;
11660                 break;
11661               default:
11662                 /* impossible */
11663                 break;
11664             }
11665             break;
11666
11667           case PGNTag:
11668             switch (lastLoadGameStart) {
11669               case GNUChessGame:
11670               case PGNTag:
11671               case MoveNumberOne:
11672               case EndOfFile:
11673                 gn--;           /* count this game */
11674                 lastLoadGameStart = cm;
11675                 break;
11676               case XBoardGame:
11677                 lastLoadGameStart = cm; /* game counted already */
11678                 break;
11679               default:
11680                 /* impossible */
11681                 break;
11682             }
11683             if (gn > 0) {
11684                 do {
11685                     yyboardindex = forwardMostMove;
11686                     cm = (ChessMove) Myylex();
11687                 } while (cm == PGNTag || cm == Comment);
11688             }
11689             break;
11690
11691           case WhiteWins:
11692           case BlackWins:
11693           case GameIsDrawn:
11694             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11695                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11696                     != CMAIL_OLD_RESULT) {
11697                     nCmailResults ++ ;
11698                     cmailResult[  CMAIL_MAX_GAMES
11699                                 - gn - 1] = CMAIL_OLD_RESULT;
11700                 }
11701             }
11702             break;
11703
11704           case NormalMove:
11705             /* Only a NormalMove can be at the start of a game
11706              * without a position diagram. */
11707             if (lastLoadGameStart == EndOfFile ) {
11708               gn--;
11709               lastLoadGameStart = MoveNumberOne;
11710             }
11711             break;
11712
11713           default:
11714             break;
11715         }
11716     }
11717
11718     if (appData.debugMode)
11719       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11720
11721     if (cm == XBoardGame) {
11722         /* Skip any header junk before position diagram and/or move 1 */
11723         for (;;) {
11724             yyboardindex = forwardMostMove;
11725             cm = (ChessMove) Myylex();
11726
11727             if (cm == EndOfFile ||
11728                 cm == GNUChessGame || cm == XBoardGame) {
11729                 /* Empty game; pretend end-of-file and handle later */
11730                 cm = EndOfFile;
11731                 break;
11732             }
11733
11734             if (cm == MoveNumberOne || cm == PositionDiagram ||
11735                 cm == PGNTag || cm == Comment)
11736               break;
11737         }
11738     } else if (cm == GNUChessGame) {
11739         if (gameInfo.event != NULL) {
11740             free(gameInfo.event);
11741         }
11742         gameInfo.event = StrSave(yy_text);
11743     }
11744
11745     startedFromSetupPosition = FALSE;
11746     while (cm == PGNTag) {
11747         if (appData.debugMode)
11748           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11749         err = ParsePGNTag(yy_text, &gameInfo);
11750         if (!err) numPGNTags++;
11751
11752         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11753         if(gameInfo.variant != oldVariant) {
11754             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11755             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11756             InitPosition(TRUE);
11757             oldVariant = gameInfo.variant;
11758             if (appData.debugMode)
11759               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11760         }
11761
11762
11763         if (gameInfo.fen != NULL) {
11764           Board initial_position;
11765           startedFromSetupPosition = TRUE;
11766           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11767             Reset(TRUE, TRUE);
11768             DisplayError(_("Bad FEN position in file"), 0);
11769             return FALSE;
11770           }
11771           CopyBoard(boards[0], initial_position);
11772           if (blackPlaysFirst) {
11773             currentMove = forwardMostMove = backwardMostMove = 1;
11774             CopyBoard(boards[1], initial_position);
11775             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11776             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11777             timeRemaining[0][1] = whiteTimeRemaining;
11778             timeRemaining[1][1] = blackTimeRemaining;
11779             if (commentList[0] != NULL) {
11780               commentList[1] = commentList[0];
11781               commentList[0] = NULL;
11782             }
11783           } else {
11784             currentMove = forwardMostMove = backwardMostMove = 0;
11785           }
11786           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11787           {   int i;
11788               initialRulePlies = FENrulePlies;
11789               for( i=0; i< nrCastlingRights; i++ )
11790                   initialRights[i] = initial_position[CASTLING][i];
11791           }
11792           yyboardindex = forwardMostMove;
11793           free(gameInfo.fen);
11794           gameInfo.fen = NULL;
11795         }
11796
11797         yyboardindex = forwardMostMove;
11798         cm = (ChessMove) Myylex();
11799
11800         /* Handle comments interspersed among the tags */
11801         while (cm == Comment) {
11802             char *p;
11803             if (appData.debugMode)
11804               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11805             p = yy_text;
11806             AppendComment(currentMove, p, FALSE);
11807             yyboardindex = forwardMostMove;
11808             cm = (ChessMove) Myylex();
11809         }
11810     }
11811
11812     /* don't rely on existence of Event tag since if game was
11813      * pasted from clipboard the Event tag may not exist
11814      */
11815     if (numPGNTags > 0){
11816         char *tags;
11817         if (gameInfo.variant == VariantNormal) {
11818           VariantClass v = StringToVariant(gameInfo.event);
11819           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11820           if(v < VariantShogi) gameInfo.variant = v;
11821         }
11822         if (!matchMode) {
11823           if( appData.autoDisplayTags ) {
11824             tags = PGNTags(&gameInfo);
11825             TagsPopUp(tags, CmailMsg());
11826             free(tags);
11827           }
11828         }
11829     } else {
11830         /* Make something up, but don't display it now */
11831         SetGameInfo();
11832         TagsPopDown();
11833     }
11834
11835     if (cm == PositionDiagram) {
11836         int i, j;
11837         char *p;
11838         Board initial_position;
11839
11840         if (appData.debugMode)
11841           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11842
11843         if (!startedFromSetupPosition) {
11844             p = yy_text;
11845             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11846               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11847                 switch (*p) {
11848                   case '{':
11849                   case '[':
11850                   case '-':
11851                   case ' ':
11852                   case '\t':
11853                   case '\n':
11854                   case '\r':
11855                     break;
11856                   default:
11857                     initial_position[i][j++] = CharToPiece(*p);
11858                     break;
11859                 }
11860             while (*p == ' ' || *p == '\t' ||
11861                    *p == '\n' || *p == '\r') p++;
11862
11863             if (strncmp(p, "black", strlen("black"))==0)
11864               blackPlaysFirst = TRUE;
11865             else
11866               blackPlaysFirst = FALSE;
11867             startedFromSetupPosition = TRUE;
11868
11869             CopyBoard(boards[0], initial_position);
11870             if (blackPlaysFirst) {
11871                 currentMove = forwardMostMove = backwardMostMove = 1;
11872                 CopyBoard(boards[1], initial_position);
11873                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11874                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11875                 timeRemaining[0][1] = whiteTimeRemaining;
11876                 timeRemaining[1][1] = blackTimeRemaining;
11877                 if (commentList[0] != NULL) {
11878                     commentList[1] = commentList[0];
11879                     commentList[0] = NULL;
11880                 }
11881             } else {
11882                 currentMove = forwardMostMove = backwardMostMove = 0;
11883             }
11884         }
11885         yyboardindex = forwardMostMove;
11886         cm = (ChessMove) Myylex();
11887     }
11888
11889     if (first.pr == NoProc) {
11890         StartChessProgram(&first);
11891     }
11892     InitChessProgram(&first, FALSE);
11893     SendToProgram("force\n", &first);
11894     if (startedFromSetupPosition) {
11895         SendBoard(&first, forwardMostMove);
11896     if (appData.debugMode) {
11897         fprintf(debugFP, "Load Game\n");
11898     }
11899         DisplayBothClocks();
11900     }
11901
11902     /* [HGM] server: flag to write setup moves in broadcast file as one */
11903     loadFlag = appData.suppressLoadMoves;
11904
11905     while (cm == Comment) {
11906         char *p;
11907         if (appData.debugMode)
11908           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11909         p = yy_text;
11910         AppendComment(currentMove, p, FALSE);
11911         yyboardindex = forwardMostMove;
11912         cm = (ChessMove) Myylex();
11913     }
11914
11915     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11916         cm == WhiteWins || cm == BlackWins ||
11917         cm == GameIsDrawn || cm == GameUnfinished) {
11918         DisplayMessage("", _("No moves in game"));
11919         if (cmailMsgLoaded) {
11920             if (appData.debugMode)
11921               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11922             ClearHighlights();
11923             flipView = FALSE;
11924         }
11925         DrawPosition(FALSE, boards[currentMove]);
11926         DisplayBothClocks();
11927         gameMode = EditGame;
11928         ModeHighlight();
11929         gameFileFP = NULL;
11930         cmailOldMove = 0;
11931         return TRUE;
11932     }
11933
11934     // [HGM] PV info: routine tests if comment empty
11935     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11936         DisplayComment(currentMove - 1, commentList[currentMove]);
11937     }
11938     if (!matchMode && appData.timeDelay != 0)
11939       DrawPosition(FALSE, boards[currentMove]);
11940
11941     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11942       programStats.ok_to_send = 1;
11943     }
11944
11945     /* if the first token after the PGN tags is a move
11946      * and not move number 1, retrieve it from the parser
11947      */
11948     if (cm != MoveNumberOne)
11949         LoadGameOneMove(cm);
11950
11951     /* load the remaining moves from the file */
11952     while (LoadGameOneMove(EndOfFile)) {
11953       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11954       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11955     }
11956
11957     /* rewind to the start of the game */
11958     currentMove = backwardMostMove;
11959
11960     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11961
11962     if (oldGameMode == AnalyzeFile ||
11963         oldGameMode == AnalyzeMode) {
11964       AnalyzeFileEvent();
11965     }
11966
11967     if (!matchMode && pos >= 0) {
11968         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11969     } else
11970     if (matchMode || appData.timeDelay == 0) {
11971       ToEndEvent();
11972     } else if (appData.timeDelay > 0) {
11973       AutoPlayGameLoop();
11974     }
11975
11976     if (appData.debugMode)
11977         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11978
11979     loadFlag = 0; /* [HGM] true game starts */
11980     return TRUE;
11981 }
11982
11983 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11984 int
11985 ReloadPosition(offset)
11986      int offset;
11987 {
11988     int positionNumber = lastLoadPositionNumber + offset;
11989     if (lastLoadPositionFP == NULL) {
11990         DisplayError(_("No position has been loaded yet"), 0);
11991         return FALSE;
11992     }
11993     if (positionNumber <= 0) {
11994         DisplayError(_("Can't back up any further"), 0);
11995         return FALSE;
11996     }
11997     return LoadPosition(lastLoadPositionFP, positionNumber,
11998                         lastLoadPositionTitle);
11999 }
12000
12001 /* Load the nth position from the given file */
12002 int
12003 LoadPositionFromFile(filename, n, title)
12004      char *filename;
12005      int n;
12006      char *title;
12007 {
12008     FILE *f;
12009     char buf[MSG_SIZ];
12010
12011     if (strcmp(filename, "-") == 0) {
12012         return LoadPosition(stdin, n, "stdin");
12013     } else {
12014         f = fopen(filename, "rb");
12015         if (f == NULL) {
12016             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12017             DisplayError(buf, errno);
12018             return FALSE;
12019         } else {
12020             return LoadPosition(f, n, title);
12021         }
12022     }
12023 }
12024
12025 /* Load the nth position from the given open file, and close it */
12026 int
12027 LoadPosition(f, positionNumber, title)
12028      FILE *f;
12029      int positionNumber;
12030      char *title;
12031 {
12032     char *p, line[MSG_SIZ];
12033     Board initial_position;
12034     int i, j, fenMode, pn;
12035
12036     if (gameMode == Training )
12037         SetTrainingModeOff();
12038
12039     if (gameMode != BeginningOfGame) {
12040         Reset(FALSE, TRUE);
12041     }
12042     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12043         fclose(lastLoadPositionFP);
12044     }
12045     if (positionNumber == 0) positionNumber = 1;
12046     lastLoadPositionFP = f;
12047     lastLoadPositionNumber = positionNumber;
12048     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12049     if (first.pr == NoProc && !appData.noChessProgram) {
12050       StartChessProgram(&first);
12051       InitChessProgram(&first, FALSE);
12052     }
12053     pn = positionNumber;
12054     if (positionNumber < 0) {
12055         /* Negative position number means to seek to that byte offset */
12056         if (fseek(f, -positionNumber, 0) == -1) {
12057             DisplayError(_("Can't seek on position file"), 0);
12058             return FALSE;
12059         };
12060         pn = 1;
12061     } else {
12062         if (fseek(f, 0, 0) == -1) {
12063             if (f == lastLoadPositionFP ?
12064                 positionNumber == lastLoadPositionNumber + 1 :
12065                 positionNumber == 1) {
12066                 pn = 1;
12067             } else {
12068                 DisplayError(_("Can't seek on position file"), 0);
12069                 return FALSE;
12070             }
12071         }
12072     }
12073     /* See if this file is FEN or old-style xboard */
12074     if (fgets(line, MSG_SIZ, f) == NULL) {
12075         DisplayError(_("Position not found in file"), 0);
12076         return FALSE;
12077     }
12078     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12079     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12080
12081     if (pn >= 2) {
12082         if (fenMode || line[0] == '#') pn--;
12083         while (pn > 0) {
12084             /* skip positions before number pn */
12085             if (fgets(line, MSG_SIZ, f) == NULL) {
12086                 Reset(TRUE, TRUE);
12087                 DisplayError(_("Position not found in file"), 0);
12088                 return FALSE;
12089             }
12090             if (fenMode || line[0] == '#') pn--;
12091         }
12092     }
12093
12094     if (fenMode) {
12095         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12096             DisplayError(_("Bad FEN position in file"), 0);
12097             return FALSE;
12098         }
12099     } else {
12100         (void) fgets(line, MSG_SIZ, f);
12101         (void) fgets(line, MSG_SIZ, f);
12102
12103         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12104             (void) fgets(line, MSG_SIZ, f);
12105             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12106                 if (*p == ' ')
12107                   continue;
12108                 initial_position[i][j++] = CharToPiece(*p);
12109             }
12110         }
12111
12112         blackPlaysFirst = FALSE;
12113         if (!feof(f)) {
12114             (void) fgets(line, MSG_SIZ, f);
12115             if (strncmp(line, "black", strlen("black"))==0)
12116               blackPlaysFirst = TRUE;
12117         }
12118     }
12119     startedFromSetupPosition = TRUE;
12120
12121     CopyBoard(boards[0], initial_position);
12122     if (blackPlaysFirst) {
12123         currentMove = forwardMostMove = backwardMostMove = 1;
12124         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12125         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12126         CopyBoard(boards[1], initial_position);
12127         DisplayMessage("", _("Black to play"));
12128     } else {
12129         currentMove = forwardMostMove = backwardMostMove = 0;
12130         DisplayMessage("", _("White to play"));
12131     }
12132     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12133     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12134         SendToProgram("force\n", &first);
12135         SendBoard(&first, forwardMostMove);
12136     }
12137     if (appData.debugMode) {
12138 int i, j;
12139   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12140   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12141         fprintf(debugFP, "Load Position\n");
12142     }
12143
12144     if (positionNumber > 1) {
12145       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12146         DisplayTitle(line);
12147     } else {
12148         DisplayTitle(title);
12149     }
12150     gameMode = EditGame;
12151     ModeHighlight();
12152     ResetClocks();
12153     timeRemaining[0][1] = whiteTimeRemaining;
12154     timeRemaining[1][1] = blackTimeRemaining;
12155     DrawPosition(FALSE, boards[currentMove]);
12156
12157     return TRUE;
12158 }
12159
12160
12161 void
12162 CopyPlayerNameIntoFileName(dest, src)
12163      char **dest, *src;
12164 {
12165     while (*src != NULLCHAR && *src != ',') {
12166         if (*src == ' ') {
12167             *(*dest)++ = '_';
12168             src++;
12169         } else {
12170             *(*dest)++ = *src++;
12171         }
12172     }
12173 }
12174
12175 char *DefaultFileName(ext)
12176      char *ext;
12177 {
12178     static char def[MSG_SIZ];
12179     char *p;
12180
12181     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12182         p = def;
12183         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12184         *p++ = '-';
12185         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12186         *p++ = '.';
12187         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12188     } else {
12189         def[0] = NULLCHAR;
12190     }
12191     return def;
12192 }
12193
12194 /* Save the current game to the given file */
12195 int
12196 SaveGameToFile(filename, append)
12197      char *filename;
12198      int append;
12199 {
12200     FILE *f;
12201     char buf[MSG_SIZ];
12202     int result, i, t,tot=0;
12203
12204     if (strcmp(filename, "-") == 0) {
12205         return SaveGame(stdout, 0, NULL);
12206     } else {
12207         for(i=0; i<10; i++) { // upto 10 tries
12208              f = fopen(filename, append ? "a" : "w");
12209              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12210              if(f || errno != 13) break;
12211              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12212              tot += t;
12213         }
12214         if (f == NULL) {
12215             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12216             DisplayError(buf, errno);
12217             return FALSE;
12218         } else {
12219             safeStrCpy(buf, lastMsg, MSG_SIZ);
12220             DisplayMessage(_("Waiting for access to save file"), "");
12221             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12222             DisplayMessage(_("Saving game"), "");
12223             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12224             result = SaveGame(f, 0, NULL);
12225             DisplayMessage(buf, "");
12226             return result;
12227         }
12228     }
12229 }
12230
12231 char *
12232 SavePart(str)
12233      char *str;
12234 {
12235     static char buf[MSG_SIZ];
12236     char *p;
12237
12238     p = strchr(str, ' ');
12239     if (p == NULL) return str;
12240     strncpy(buf, str, p - str);
12241     buf[p - str] = NULLCHAR;
12242     return buf;
12243 }
12244
12245 #define PGN_MAX_LINE 75
12246
12247 #define PGN_SIDE_WHITE  0
12248 #define PGN_SIDE_BLACK  1
12249
12250 /* [AS] */
12251 static int FindFirstMoveOutOfBook( int side )
12252 {
12253     int result = -1;
12254
12255     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12256         int index = backwardMostMove;
12257         int has_book_hit = 0;
12258
12259         if( (index % 2) != side ) {
12260             index++;
12261         }
12262
12263         while( index < forwardMostMove ) {
12264             /* Check to see if engine is in book */
12265             int depth = pvInfoList[index].depth;
12266             int score = pvInfoList[index].score;
12267             int in_book = 0;
12268
12269             if( depth <= 2 ) {
12270                 in_book = 1;
12271             }
12272             else if( score == 0 && depth == 63 ) {
12273                 in_book = 1; /* Zappa */
12274             }
12275             else if( score == 2 && depth == 99 ) {
12276                 in_book = 1; /* Abrok */
12277             }
12278
12279             has_book_hit += in_book;
12280
12281             if( ! in_book ) {
12282                 result = index;
12283
12284                 break;
12285             }
12286
12287             index += 2;
12288         }
12289     }
12290
12291     return result;
12292 }
12293
12294 /* [AS] */
12295 void GetOutOfBookInfo( char * buf )
12296 {
12297     int oob[2];
12298     int i;
12299     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12300
12301     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12302     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12303
12304     *buf = '\0';
12305
12306     if( oob[0] >= 0 || oob[1] >= 0 ) {
12307         for( i=0; i<2; i++ ) {
12308             int idx = oob[i];
12309
12310             if( idx >= 0 ) {
12311                 if( i > 0 && oob[0] >= 0 ) {
12312                     strcat( buf, "   " );
12313                 }
12314
12315                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12316                 sprintf( buf+strlen(buf), "%s%.2f",
12317                     pvInfoList[idx].score >= 0 ? "+" : "",
12318                     pvInfoList[idx].score / 100.0 );
12319             }
12320         }
12321     }
12322 }
12323
12324 /* Save game in PGN style and close the file */
12325 int
12326 SaveGamePGN(f)
12327      FILE *f;
12328 {
12329     int i, offset, linelen, newblock;
12330     time_t tm;
12331 //    char *movetext;
12332     char numtext[32];
12333     int movelen, numlen, blank;
12334     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12335
12336     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12337
12338     tm = time((time_t *) NULL);
12339
12340     PrintPGNTags(f, &gameInfo);
12341
12342     if (backwardMostMove > 0 || startedFromSetupPosition) {
12343         char *fen = PositionToFEN(backwardMostMove, NULL);
12344         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12345         fprintf(f, "\n{--------------\n");
12346         PrintPosition(f, backwardMostMove);
12347         fprintf(f, "--------------}\n");
12348         free(fen);
12349     }
12350     else {
12351         /* [AS] Out of book annotation */
12352         if( appData.saveOutOfBookInfo ) {
12353             char buf[64];
12354
12355             GetOutOfBookInfo( buf );
12356
12357             if( buf[0] != '\0' ) {
12358                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12359             }
12360         }
12361
12362         fprintf(f, "\n");
12363     }
12364
12365     i = backwardMostMove;
12366     linelen = 0;
12367     newblock = TRUE;
12368
12369     while (i < forwardMostMove) {
12370         /* Print comments preceding this move */
12371         if (commentList[i] != NULL) {
12372             if (linelen > 0) fprintf(f, "\n");
12373             fprintf(f, "%s", commentList[i]);
12374             linelen = 0;
12375             newblock = TRUE;
12376         }
12377
12378         /* Format move number */
12379         if ((i % 2) == 0)
12380           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12381         else
12382           if (newblock)
12383             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12384           else
12385             numtext[0] = NULLCHAR;
12386
12387         numlen = strlen(numtext);
12388         newblock = FALSE;
12389
12390         /* Print move number */
12391         blank = linelen > 0 && numlen > 0;
12392         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12393             fprintf(f, "\n");
12394             linelen = 0;
12395             blank = 0;
12396         }
12397         if (blank) {
12398             fprintf(f, " ");
12399             linelen++;
12400         }
12401         fprintf(f, "%s", numtext);
12402         linelen += numlen;
12403
12404         /* Get move */
12405         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12406         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12407
12408         /* Print move */
12409         blank = linelen > 0 && movelen > 0;
12410         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12411             fprintf(f, "\n");
12412             linelen = 0;
12413             blank = 0;
12414         }
12415         if (blank) {
12416             fprintf(f, " ");
12417             linelen++;
12418         }
12419         fprintf(f, "%s", move_buffer);
12420         linelen += movelen;
12421
12422         /* [AS] Add PV info if present */
12423         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12424             /* [HGM] add time */
12425             char buf[MSG_SIZ]; int seconds;
12426
12427             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12428
12429             if( seconds <= 0)
12430               buf[0] = 0;
12431             else
12432               if( seconds < 30 )
12433                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12434               else
12435                 {
12436                   seconds = (seconds + 4)/10; // round to full seconds
12437                   if( seconds < 60 )
12438                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12439                   else
12440                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12441                 }
12442
12443             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12444                       pvInfoList[i].score >= 0 ? "+" : "",
12445                       pvInfoList[i].score / 100.0,
12446                       pvInfoList[i].depth,
12447                       buf );
12448
12449             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12450
12451             /* Print score/depth */
12452             blank = linelen > 0 && movelen > 0;
12453             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12454                 fprintf(f, "\n");
12455                 linelen = 0;
12456                 blank = 0;
12457             }
12458             if (blank) {
12459                 fprintf(f, " ");
12460                 linelen++;
12461             }
12462             fprintf(f, "%s", move_buffer);
12463             linelen += movelen;
12464         }
12465
12466         i++;
12467     }
12468
12469     /* Start a new line */
12470     if (linelen > 0) fprintf(f, "\n");
12471
12472     /* Print comments after last move */
12473     if (commentList[i] != NULL) {
12474         fprintf(f, "%s\n", commentList[i]);
12475     }
12476
12477     /* Print result */
12478     if (gameInfo.resultDetails != NULL &&
12479         gameInfo.resultDetails[0] != NULLCHAR) {
12480         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12481                 PGNResult(gameInfo.result));
12482     } else {
12483         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12484     }
12485
12486     fclose(f);
12487     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12488     return TRUE;
12489 }
12490
12491 /* Save game in old style and close the file */
12492 int
12493 SaveGameOldStyle(f)
12494      FILE *f;
12495 {
12496     int i, offset;
12497     time_t tm;
12498
12499     tm = time((time_t *) NULL);
12500
12501     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12502     PrintOpponents(f);
12503
12504     if (backwardMostMove > 0 || startedFromSetupPosition) {
12505         fprintf(f, "\n[--------------\n");
12506         PrintPosition(f, backwardMostMove);
12507         fprintf(f, "--------------]\n");
12508     } else {
12509         fprintf(f, "\n");
12510     }
12511
12512     i = backwardMostMove;
12513     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12514
12515     while (i < forwardMostMove) {
12516         if (commentList[i] != NULL) {
12517             fprintf(f, "[%s]\n", commentList[i]);
12518         }
12519
12520         if ((i % 2) == 1) {
12521             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12522             i++;
12523         } else {
12524             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12525             i++;
12526             if (commentList[i] != NULL) {
12527                 fprintf(f, "\n");
12528                 continue;
12529             }
12530             if (i >= forwardMostMove) {
12531                 fprintf(f, "\n");
12532                 break;
12533             }
12534             fprintf(f, "%s\n", parseList[i]);
12535             i++;
12536         }
12537     }
12538
12539     if (commentList[i] != NULL) {
12540         fprintf(f, "[%s]\n", commentList[i]);
12541     }
12542
12543     /* This isn't really the old style, but it's close enough */
12544     if (gameInfo.resultDetails != NULL &&
12545         gameInfo.resultDetails[0] != NULLCHAR) {
12546         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12547                 gameInfo.resultDetails);
12548     } else {
12549         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12550     }
12551
12552     fclose(f);
12553     return TRUE;
12554 }
12555
12556 /* Save the current game to open file f and close the file */
12557 int
12558 SaveGame(f, dummy, dummy2)
12559      FILE *f;
12560      int dummy;
12561      char *dummy2;
12562 {
12563     if (gameMode == EditPosition) EditPositionDone(TRUE);
12564     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12565     if (appData.oldSaveStyle)
12566       return SaveGameOldStyle(f);
12567     else
12568       return SaveGamePGN(f);
12569 }
12570
12571 /* Save the current position to the given file */
12572 int
12573 SavePositionToFile(filename)
12574      char *filename;
12575 {
12576     FILE *f;
12577     char buf[MSG_SIZ];
12578
12579     if (strcmp(filename, "-") == 0) {
12580         return SavePosition(stdout, 0, NULL);
12581     } else {
12582         f = fopen(filename, "a");
12583         if (f == NULL) {
12584             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12585             DisplayError(buf, errno);
12586             return FALSE;
12587         } else {
12588             safeStrCpy(buf, lastMsg, MSG_SIZ);
12589             DisplayMessage(_("Waiting for access to save file"), "");
12590             flock(fileno(f), LOCK_EX); // [HGM] lock
12591             DisplayMessage(_("Saving position"), "");
12592             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12593             SavePosition(f, 0, NULL);
12594             DisplayMessage(buf, "");
12595             return TRUE;
12596         }
12597     }
12598 }
12599
12600 /* Save the current position to the given open file and close the file */
12601 int
12602 SavePosition(f, dummy, dummy2)
12603      FILE *f;
12604      int dummy;
12605      char *dummy2;
12606 {
12607     time_t tm;
12608     char *fen;
12609
12610     if (gameMode == EditPosition) EditPositionDone(TRUE);
12611     if (appData.oldSaveStyle) {
12612         tm = time((time_t *) NULL);
12613
12614         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12615         PrintOpponents(f);
12616         fprintf(f, "[--------------\n");
12617         PrintPosition(f, currentMove);
12618         fprintf(f, "--------------]\n");
12619     } else {
12620         fen = PositionToFEN(currentMove, NULL);
12621         fprintf(f, "%s\n", fen);
12622         free(fen);
12623     }
12624     fclose(f);
12625     return TRUE;
12626 }
12627
12628 void
12629 ReloadCmailMsgEvent(unregister)
12630      int unregister;
12631 {
12632 #if !WIN32
12633     static char *inFilename = NULL;
12634     static char *outFilename;
12635     int i;
12636     struct stat inbuf, outbuf;
12637     int status;
12638
12639     /* Any registered moves are unregistered if unregister is set, */
12640     /* i.e. invoked by the signal handler */
12641     if (unregister) {
12642         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12643             cmailMoveRegistered[i] = FALSE;
12644             if (cmailCommentList[i] != NULL) {
12645                 free(cmailCommentList[i]);
12646                 cmailCommentList[i] = NULL;
12647             }
12648         }
12649         nCmailMovesRegistered = 0;
12650     }
12651
12652     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12653         cmailResult[i] = CMAIL_NOT_RESULT;
12654     }
12655     nCmailResults = 0;
12656
12657     if (inFilename == NULL) {
12658         /* Because the filenames are static they only get malloced once  */
12659         /* and they never get freed                                      */
12660         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12661         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12662
12663         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12664         sprintf(outFilename, "%s.out", appData.cmailGameName);
12665     }
12666
12667     status = stat(outFilename, &outbuf);
12668     if (status < 0) {
12669         cmailMailedMove = FALSE;
12670     } else {
12671         status = stat(inFilename, &inbuf);
12672         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12673     }
12674
12675     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12676        counts the games, notes how each one terminated, etc.
12677
12678        It would be nice to remove this kludge and instead gather all
12679        the information while building the game list.  (And to keep it
12680        in the game list nodes instead of having a bunch of fixed-size
12681        parallel arrays.)  Note this will require getting each game's
12682        termination from the PGN tags, as the game list builder does
12683        not process the game moves.  --mann
12684        */
12685     cmailMsgLoaded = TRUE;
12686     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12687
12688     /* Load first game in the file or popup game menu */
12689     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12690
12691 #endif /* !WIN32 */
12692     return;
12693 }
12694
12695 int
12696 RegisterMove()
12697 {
12698     FILE *f;
12699     char string[MSG_SIZ];
12700
12701     if (   cmailMailedMove
12702         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12703         return TRUE;            /* Allow free viewing  */
12704     }
12705
12706     /* Unregister move to ensure that we don't leave RegisterMove        */
12707     /* with the move registered when the conditions for registering no   */
12708     /* longer hold                                                       */
12709     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12710         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12711         nCmailMovesRegistered --;
12712
12713         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12714           {
12715               free(cmailCommentList[lastLoadGameNumber - 1]);
12716               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12717           }
12718     }
12719
12720     if (cmailOldMove == -1) {
12721         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12722         return FALSE;
12723     }
12724
12725     if (currentMove > cmailOldMove + 1) {
12726         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12727         return FALSE;
12728     }
12729
12730     if (currentMove < cmailOldMove) {
12731         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12732         return FALSE;
12733     }
12734
12735     if (forwardMostMove > currentMove) {
12736         /* Silently truncate extra moves */
12737         TruncateGame();
12738     }
12739
12740     if (   (currentMove == cmailOldMove + 1)
12741         || (   (currentMove == cmailOldMove)
12742             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12743                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12744         if (gameInfo.result != GameUnfinished) {
12745             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12746         }
12747
12748         if (commentList[currentMove] != NULL) {
12749             cmailCommentList[lastLoadGameNumber - 1]
12750               = StrSave(commentList[currentMove]);
12751         }
12752         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12753
12754         if (appData.debugMode)
12755           fprintf(debugFP, "Saving %s for game %d\n",
12756                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12757
12758         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12759
12760         f = fopen(string, "w");
12761         if (appData.oldSaveStyle) {
12762             SaveGameOldStyle(f); /* also closes the file */
12763
12764             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12765             f = fopen(string, "w");
12766             SavePosition(f, 0, NULL); /* also closes the file */
12767         } else {
12768             fprintf(f, "{--------------\n");
12769             PrintPosition(f, currentMove);
12770             fprintf(f, "--------------}\n\n");
12771
12772             SaveGame(f, 0, NULL); /* also closes the file*/
12773         }
12774
12775         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12776         nCmailMovesRegistered ++;
12777     } else if (nCmailGames == 1) {
12778         DisplayError(_("You have not made a move yet"), 0);
12779         return FALSE;
12780     }
12781
12782     return TRUE;
12783 }
12784
12785 void
12786 MailMoveEvent()
12787 {
12788 #if !WIN32
12789     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12790     FILE *commandOutput;
12791     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12792     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12793     int nBuffers;
12794     int i;
12795     int archived;
12796     char *arcDir;
12797
12798     if (! cmailMsgLoaded) {
12799         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12800         return;
12801     }
12802
12803     if (nCmailGames == nCmailResults) {
12804         DisplayError(_("No unfinished games"), 0);
12805         return;
12806     }
12807
12808 #if CMAIL_PROHIBIT_REMAIL
12809     if (cmailMailedMove) {
12810       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);
12811         DisplayError(msg, 0);
12812         return;
12813     }
12814 #endif
12815
12816     if (! (cmailMailedMove || RegisterMove())) return;
12817
12818     if (   cmailMailedMove
12819         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12820       snprintf(string, MSG_SIZ, partCommandString,
12821                appData.debugMode ? " -v" : "", appData.cmailGameName);
12822         commandOutput = popen(string, "r");
12823
12824         if (commandOutput == NULL) {
12825             DisplayError(_("Failed to invoke cmail"), 0);
12826         } else {
12827             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12828                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12829             }
12830             if (nBuffers > 1) {
12831                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12832                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12833                 nBytes = MSG_SIZ - 1;
12834             } else {
12835                 (void) memcpy(msg, buffer, nBytes);
12836             }
12837             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12838
12839             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12840                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12841
12842                 archived = TRUE;
12843                 for (i = 0; i < nCmailGames; i ++) {
12844                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12845                         archived = FALSE;
12846                     }
12847                 }
12848                 if (   archived
12849                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12850                         != NULL)) {
12851                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12852                            arcDir,
12853                            appData.cmailGameName,
12854                            gameInfo.date);
12855                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12856                     cmailMsgLoaded = FALSE;
12857                 }
12858             }
12859
12860             DisplayInformation(msg);
12861             pclose(commandOutput);
12862         }
12863     } else {
12864         if ((*cmailMsg) != '\0') {
12865             DisplayInformation(cmailMsg);
12866         }
12867     }
12868
12869     return;
12870 #endif /* !WIN32 */
12871 }
12872
12873 char *
12874 CmailMsg()
12875 {
12876 #if WIN32
12877     return NULL;
12878 #else
12879     int  prependComma = 0;
12880     char number[5];
12881     char string[MSG_SIZ];       /* Space for game-list */
12882     int  i;
12883
12884     if (!cmailMsgLoaded) return "";
12885
12886     if (cmailMailedMove) {
12887       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12888     } else {
12889         /* Create a list of games left */
12890       snprintf(string, MSG_SIZ, "[");
12891         for (i = 0; i < nCmailGames; i ++) {
12892             if (! (   cmailMoveRegistered[i]
12893                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12894                 if (prependComma) {
12895                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12896                 } else {
12897                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12898                     prependComma = 1;
12899                 }
12900
12901                 strcat(string, number);
12902             }
12903         }
12904         strcat(string, "]");
12905
12906         if (nCmailMovesRegistered + nCmailResults == 0) {
12907             switch (nCmailGames) {
12908               case 1:
12909                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12910                 break;
12911
12912               case 2:
12913                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12914                 break;
12915
12916               default:
12917                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12918                          nCmailGames);
12919                 break;
12920             }
12921         } else {
12922             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12923               case 1:
12924                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12925                          string);
12926                 break;
12927
12928               case 0:
12929                 if (nCmailResults == nCmailGames) {
12930                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12931                 } else {
12932                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12933                 }
12934                 break;
12935
12936               default:
12937                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12938                          string);
12939             }
12940         }
12941     }
12942     return cmailMsg;
12943 #endif /* WIN32 */
12944 }
12945
12946 void
12947 ResetGameEvent()
12948 {
12949     if (gameMode == Training)
12950       SetTrainingModeOff();
12951
12952     Reset(TRUE, TRUE);
12953     cmailMsgLoaded = FALSE;
12954     if (appData.icsActive) {
12955       SendToICS(ics_prefix);
12956       SendToICS("refresh\n");
12957     }
12958 }
12959
12960 void
12961 ExitEvent(status)
12962      int status;
12963 {
12964     exiting++;
12965     if (exiting > 2) {
12966       /* Give up on clean exit */
12967       exit(status);
12968     }
12969     if (exiting > 1) {
12970       /* Keep trying for clean exit */
12971       return;
12972     }
12973
12974     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12975
12976     if (telnetISR != NULL) {
12977       RemoveInputSource(telnetISR);
12978     }
12979     if (icsPR != NoProc) {
12980       DestroyChildProcess(icsPR, TRUE);
12981     }
12982
12983     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12984     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12985
12986     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12987     /* make sure this other one finishes before killing it!                  */
12988     if(endingGame) { int count = 0;
12989         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12990         while(endingGame && count++ < 10) DoSleep(1);
12991         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12992     }
12993
12994     /* Kill off chess programs */
12995     if (first.pr != NoProc) {
12996         ExitAnalyzeMode();
12997
12998         DoSleep( appData.delayBeforeQuit );
12999         SendToProgram("quit\n", &first);
13000         DoSleep( appData.delayAfterQuit );
13001         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13002     }
13003     if (second.pr != NoProc) {
13004         DoSleep( appData.delayBeforeQuit );
13005         SendToProgram("quit\n", &second);
13006         DoSleep( appData.delayAfterQuit );
13007         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13008     }
13009     if (first.isr != NULL) {
13010         RemoveInputSource(first.isr);
13011     }
13012     if (second.isr != NULL) {
13013         RemoveInputSource(second.isr);
13014     }
13015
13016     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13017     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13018
13019     ShutDownFrontEnd();
13020     exit(status);
13021 }
13022
13023 void
13024 PauseEvent()
13025 {
13026     if (appData.debugMode)
13027         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13028     if (pausing) {
13029         pausing = FALSE;
13030         ModeHighlight();
13031         if (gameMode == MachinePlaysWhite ||
13032             gameMode == MachinePlaysBlack) {
13033             StartClocks();
13034         } else {
13035             DisplayBothClocks();
13036         }
13037         if (gameMode == PlayFromGameFile) {
13038             if (appData.timeDelay >= 0)
13039                 AutoPlayGameLoop();
13040         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13041             Reset(FALSE, TRUE);
13042             SendToICS(ics_prefix);
13043             SendToICS("refresh\n");
13044         } else if (currentMove < forwardMostMove) {
13045             ForwardInner(forwardMostMove);
13046         }
13047         pauseExamInvalid = FALSE;
13048     } else {
13049         switch (gameMode) {
13050           default:
13051             return;
13052           case IcsExamining:
13053             pauseExamForwardMostMove = forwardMostMove;
13054             pauseExamInvalid = FALSE;
13055             /* fall through */
13056           case IcsObserving:
13057           case IcsPlayingWhite:
13058           case IcsPlayingBlack:
13059             pausing = TRUE;
13060             ModeHighlight();
13061             return;
13062           case PlayFromGameFile:
13063             (void) StopLoadGameTimer();
13064             pausing = TRUE;
13065             ModeHighlight();
13066             break;
13067           case BeginningOfGame:
13068             if (appData.icsActive) return;
13069             /* else fall through */
13070           case MachinePlaysWhite:
13071           case MachinePlaysBlack:
13072           case TwoMachinesPlay:
13073             if (forwardMostMove == 0)
13074               return;           /* don't pause if no one has moved */
13075             if ((gameMode == MachinePlaysWhite &&
13076                  !WhiteOnMove(forwardMostMove)) ||
13077                 (gameMode == MachinePlaysBlack &&
13078                  WhiteOnMove(forwardMostMove))) {
13079                 StopClocks();
13080             }
13081             pausing = TRUE;
13082             ModeHighlight();
13083             break;
13084         }
13085     }
13086 }
13087
13088 void
13089 EditCommentEvent()
13090 {
13091     char title[MSG_SIZ];
13092
13093     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13094       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13095     } else {
13096       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13097                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13098                parseList[currentMove - 1]);
13099     }
13100
13101     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13102 }
13103
13104
13105 void
13106 EditTagsEvent()
13107 {
13108     char *tags = PGNTags(&gameInfo);
13109     bookUp = FALSE;
13110     EditTagsPopUp(tags, NULL);
13111     free(tags);
13112 }
13113
13114 void
13115 AnalyzeModeEvent()
13116 {
13117     if (appData.noChessProgram || gameMode == AnalyzeMode)
13118       return;
13119
13120     if (gameMode != AnalyzeFile) {
13121         if (!appData.icsEngineAnalyze) {
13122                EditGameEvent();
13123                if (gameMode != EditGame) return;
13124         }
13125         ResurrectChessProgram();
13126         SendToProgram("analyze\n", &first);
13127         first.analyzing = TRUE;
13128         /*first.maybeThinking = TRUE;*/
13129         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13130         EngineOutputPopUp();
13131     }
13132     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13133     pausing = FALSE;
13134     ModeHighlight();
13135     SetGameInfo();
13136
13137     StartAnalysisClock();
13138     GetTimeMark(&lastNodeCountTime);
13139     lastNodeCount = 0;
13140 }
13141
13142 void
13143 AnalyzeFileEvent()
13144 {
13145     if (appData.noChessProgram || gameMode == AnalyzeFile)
13146       return;
13147
13148     if (gameMode != AnalyzeMode) {
13149         EditGameEvent();
13150         if (gameMode != EditGame) return;
13151         ResurrectChessProgram();
13152         SendToProgram("analyze\n", &first);
13153         first.analyzing = TRUE;
13154         /*first.maybeThinking = TRUE;*/
13155         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13156         EngineOutputPopUp();
13157     }
13158     gameMode = AnalyzeFile;
13159     pausing = FALSE;
13160     ModeHighlight();
13161     SetGameInfo();
13162
13163     StartAnalysisClock();
13164     GetTimeMark(&lastNodeCountTime);
13165     lastNodeCount = 0;
13166     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13167 }
13168
13169 void
13170 MachineWhiteEvent()
13171 {
13172     char buf[MSG_SIZ];
13173     char *bookHit = NULL;
13174
13175     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13176       return;
13177
13178
13179     if (gameMode == PlayFromGameFile ||
13180         gameMode == TwoMachinesPlay  ||
13181         gameMode == Training         ||
13182         gameMode == AnalyzeMode      ||
13183         gameMode == EndOfGame)
13184         EditGameEvent();
13185
13186     if (gameMode == EditPosition)
13187         EditPositionDone(TRUE);
13188
13189     if (!WhiteOnMove(currentMove)) {
13190         DisplayError(_("It is not White's turn"), 0);
13191         return;
13192     }
13193
13194     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13195       ExitAnalyzeMode();
13196
13197     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13198         gameMode == AnalyzeFile)
13199         TruncateGame();
13200
13201     ResurrectChessProgram();    /* in case it isn't running */
13202     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13203         gameMode = MachinePlaysWhite;
13204         ResetClocks();
13205     } else
13206     gameMode = MachinePlaysWhite;
13207     pausing = FALSE;
13208     ModeHighlight();
13209     SetGameInfo();
13210     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13211     DisplayTitle(buf);
13212     if (first.sendName) {
13213       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13214       SendToProgram(buf, &first);
13215     }
13216     if (first.sendTime) {
13217       if (first.useColors) {
13218         SendToProgram("black\n", &first); /*gnu kludge*/
13219       }
13220       SendTimeRemaining(&first, TRUE);
13221     }
13222     if (first.useColors) {
13223       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13224     }
13225     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13226     SetMachineThinkingEnables();
13227     first.maybeThinking = TRUE;
13228     StartClocks();
13229     firstMove = FALSE;
13230
13231     if (appData.autoFlipView && !flipView) {
13232       flipView = !flipView;
13233       DrawPosition(FALSE, NULL);
13234       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13235     }
13236
13237     if(bookHit) { // [HGM] book: simulate book reply
13238         static char bookMove[MSG_SIZ]; // a bit generous?
13239
13240         programStats.nodes = programStats.depth = programStats.time =
13241         programStats.score = programStats.got_only_move = 0;
13242         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13243
13244         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13245         strcat(bookMove, bookHit);
13246         HandleMachineMove(bookMove, &first);
13247     }
13248 }
13249
13250 void
13251 MachineBlackEvent()
13252 {
13253   char buf[MSG_SIZ];
13254   char *bookHit = NULL;
13255
13256     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13257         return;
13258
13259
13260     if (gameMode == PlayFromGameFile ||
13261         gameMode == TwoMachinesPlay  ||
13262         gameMode == Training         ||
13263         gameMode == AnalyzeMode      ||
13264         gameMode == EndOfGame)
13265         EditGameEvent();
13266
13267     if (gameMode == EditPosition)
13268         EditPositionDone(TRUE);
13269
13270     if (WhiteOnMove(currentMove)) {
13271         DisplayError(_("It is not Black's turn"), 0);
13272         return;
13273     }
13274
13275     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13276       ExitAnalyzeMode();
13277
13278     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13279         gameMode == AnalyzeFile)
13280         TruncateGame();
13281
13282     ResurrectChessProgram();    /* in case it isn't running */
13283     gameMode = MachinePlaysBlack;
13284     pausing = FALSE;
13285     ModeHighlight();
13286     SetGameInfo();
13287     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13288     DisplayTitle(buf);
13289     if (first.sendName) {
13290       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13291       SendToProgram(buf, &first);
13292     }
13293     if (first.sendTime) {
13294       if (first.useColors) {
13295         SendToProgram("white\n", &first); /*gnu kludge*/
13296       }
13297       SendTimeRemaining(&first, FALSE);
13298     }
13299     if (first.useColors) {
13300       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13301     }
13302     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13303     SetMachineThinkingEnables();
13304     first.maybeThinking = TRUE;
13305     StartClocks();
13306
13307     if (appData.autoFlipView && flipView) {
13308       flipView = !flipView;
13309       DrawPosition(FALSE, NULL);
13310       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13311     }
13312     if(bookHit) { // [HGM] book: simulate book reply
13313         static char bookMove[MSG_SIZ]; // a bit generous?
13314
13315         programStats.nodes = programStats.depth = programStats.time =
13316         programStats.score = programStats.got_only_move = 0;
13317         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13318
13319         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13320         strcat(bookMove, bookHit);
13321         HandleMachineMove(bookMove, &first);
13322     }
13323 }
13324
13325
13326 void
13327 DisplayTwoMachinesTitle()
13328 {
13329     char buf[MSG_SIZ];
13330     if (appData.matchGames > 0) {
13331         if(appData.tourneyFile[0]) {
13332           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13333                    gameInfo.white, gameInfo.black,
13334                    nextGame+1, appData.matchGames+1,
13335                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13336         } else 
13337         if (first.twoMachinesColor[0] == 'w') {
13338           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13339                    gameInfo.white, gameInfo.black,
13340                    first.matchWins, second.matchWins,
13341                    matchGame - 1 - (first.matchWins + second.matchWins));
13342         } else {
13343           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13344                    gameInfo.white, gameInfo.black,
13345                    second.matchWins, first.matchWins,
13346                    matchGame - 1 - (first.matchWins + second.matchWins));
13347         }
13348     } else {
13349       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13350     }
13351     DisplayTitle(buf);
13352 }
13353
13354 void
13355 SettingsMenuIfReady()
13356 {
13357   if (second.lastPing != second.lastPong) {
13358     DisplayMessage("", _("Waiting for second chess program"));
13359     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13360     return;
13361   }
13362   ThawUI();
13363   DisplayMessage("", "");
13364   SettingsPopUp(&second);
13365 }
13366
13367 int
13368 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13369 {
13370     char buf[MSG_SIZ];
13371     if (cps->pr == NoProc) {
13372         StartChessProgram(cps);
13373         if (cps->protocolVersion == 1) {
13374           retry();
13375         } else {
13376           /* kludge: allow timeout for initial "feature" command */
13377           FreezeUI();
13378           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13379           DisplayMessage("", buf);
13380           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13381         }
13382         return 1;
13383     }
13384     return 0;
13385 }
13386
13387 void
13388 TwoMachinesEvent P((void))
13389 {
13390     int i;
13391     char buf[MSG_SIZ];
13392     ChessProgramState *onmove;
13393     char *bookHit = NULL;
13394     static int stalling = 0;
13395     TimeMark now;
13396     long wait;
13397
13398     if (appData.noChessProgram) return;
13399
13400     switch (gameMode) {
13401       case TwoMachinesPlay:
13402         return;
13403       case MachinePlaysWhite:
13404       case MachinePlaysBlack:
13405         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13406             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13407             return;
13408         }
13409         /* fall through */
13410       case BeginningOfGame:
13411       case PlayFromGameFile:
13412       case EndOfGame:
13413         EditGameEvent();
13414         if (gameMode != EditGame) return;
13415         break;
13416       case EditPosition:
13417         EditPositionDone(TRUE);
13418         break;
13419       case AnalyzeMode:
13420       case AnalyzeFile:
13421         ExitAnalyzeMode();
13422         break;
13423       case EditGame:
13424       default:
13425         break;
13426     }
13427
13428 //    forwardMostMove = currentMove;
13429     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13430
13431     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13432
13433     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13434     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13435       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13436       return;
13437     }
13438     if(!stalling) {
13439       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13440       SendToProgram("force\n", &second);
13441       stalling = 1;
13442       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13443       return;
13444     }
13445     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13446     if(appData.matchPause>10000 || appData.matchPause<10)
13447                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13448     wait = SubtractTimeMarks(&now, &pauseStart);
13449     if(wait < appData.matchPause) {
13450         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13451         return;
13452     }
13453     stalling = 0;
13454     DisplayMessage("", "");
13455     if (startedFromSetupPosition) {
13456         SendBoard(&second, backwardMostMove);
13457     if (appData.debugMode) {
13458         fprintf(debugFP, "Two Machines\n");
13459     }
13460     }
13461     for (i = backwardMostMove; i < forwardMostMove; i++) {
13462         SendMoveToProgram(i, &second);
13463     }
13464
13465     gameMode = TwoMachinesPlay;
13466     pausing = FALSE;
13467     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13468     SetGameInfo();
13469     DisplayTwoMachinesTitle();
13470     firstMove = TRUE;
13471     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13472         onmove = &first;
13473     } else {
13474         onmove = &second;
13475     }
13476     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13477     SendToProgram(first.computerString, &first);
13478     if (first.sendName) {
13479       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13480       SendToProgram(buf, &first);
13481     }
13482     SendToProgram(second.computerString, &second);
13483     if (second.sendName) {
13484       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13485       SendToProgram(buf, &second);
13486     }
13487
13488     ResetClocks();
13489     if (!first.sendTime || !second.sendTime) {
13490         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13491         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13492     }
13493     if (onmove->sendTime) {
13494       if (onmove->useColors) {
13495         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13496       }
13497       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13498     }
13499     if (onmove->useColors) {
13500       SendToProgram(onmove->twoMachinesColor, onmove);
13501     }
13502     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13503 //    SendToProgram("go\n", onmove);
13504     onmove->maybeThinking = TRUE;
13505     SetMachineThinkingEnables();
13506
13507     StartClocks();
13508
13509     if(bookHit) { // [HGM] book: simulate book reply
13510         static char bookMove[MSG_SIZ]; // a bit generous?
13511
13512         programStats.nodes = programStats.depth = programStats.time =
13513         programStats.score = programStats.got_only_move = 0;
13514         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13515
13516         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13517         strcat(bookMove, bookHit);
13518         savedMessage = bookMove; // args for deferred call
13519         savedState = onmove;
13520         ScheduleDelayedEvent(DeferredBookMove, 1);
13521     }
13522 }
13523
13524 void
13525 TrainingEvent()
13526 {
13527     if (gameMode == Training) {
13528       SetTrainingModeOff();
13529       gameMode = PlayFromGameFile;
13530       DisplayMessage("", _("Training mode off"));
13531     } else {
13532       gameMode = Training;
13533       animateTraining = appData.animate;
13534
13535       /* make sure we are not already at the end of the game */
13536       if (currentMove < forwardMostMove) {
13537         SetTrainingModeOn();
13538         DisplayMessage("", _("Training mode on"));
13539       } else {
13540         gameMode = PlayFromGameFile;
13541         DisplayError(_("Already at end of game"), 0);
13542       }
13543     }
13544     ModeHighlight();
13545 }
13546
13547 void
13548 IcsClientEvent()
13549 {
13550     if (!appData.icsActive) return;
13551     switch (gameMode) {
13552       case IcsPlayingWhite:
13553       case IcsPlayingBlack:
13554       case IcsObserving:
13555       case IcsIdle:
13556       case BeginningOfGame:
13557       case IcsExamining:
13558         return;
13559
13560       case EditGame:
13561         break;
13562
13563       case EditPosition:
13564         EditPositionDone(TRUE);
13565         break;
13566
13567       case AnalyzeMode:
13568       case AnalyzeFile:
13569         ExitAnalyzeMode();
13570         break;
13571
13572       default:
13573         EditGameEvent();
13574         break;
13575     }
13576
13577     gameMode = IcsIdle;
13578     ModeHighlight();
13579     return;
13580 }
13581
13582
13583 void
13584 EditGameEvent()
13585 {
13586     int i;
13587
13588     switch (gameMode) {
13589       case Training:
13590         SetTrainingModeOff();
13591         break;
13592       case MachinePlaysWhite:
13593       case MachinePlaysBlack:
13594       case BeginningOfGame:
13595         SendToProgram("force\n", &first);
13596         SetUserThinkingEnables();
13597         break;
13598       case PlayFromGameFile:
13599         (void) StopLoadGameTimer();
13600         if (gameFileFP != NULL) {
13601             gameFileFP = NULL;
13602         }
13603         break;
13604       case EditPosition:
13605         EditPositionDone(TRUE);
13606         break;
13607       case AnalyzeMode:
13608       case AnalyzeFile:
13609         ExitAnalyzeMode();
13610         SendToProgram("force\n", &first);
13611         break;
13612       case TwoMachinesPlay:
13613         GameEnds(EndOfFile, NULL, GE_PLAYER);
13614         ResurrectChessProgram();
13615         SetUserThinkingEnables();
13616         break;
13617       case EndOfGame:
13618         ResurrectChessProgram();
13619         break;
13620       case IcsPlayingBlack:
13621       case IcsPlayingWhite:
13622         DisplayError(_("Warning: You are still playing a game"), 0);
13623         break;
13624       case IcsObserving:
13625         DisplayError(_("Warning: You are still observing a game"), 0);
13626         break;
13627       case IcsExamining:
13628         DisplayError(_("Warning: You are still examining a game"), 0);
13629         break;
13630       case IcsIdle:
13631         break;
13632       case EditGame:
13633       default:
13634         return;
13635     }
13636
13637     pausing = FALSE;
13638     StopClocks();
13639     first.offeredDraw = second.offeredDraw = 0;
13640
13641     if (gameMode == PlayFromGameFile) {
13642         whiteTimeRemaining = timeRemaining[0][currentMove];
13643         blackTimeRemaining = timeRemaining[1][currentMove];
13644         DisplayTitle("");
13645     }
13646
13647     if (gameMode == MachinePlaysWhite ||
13648         gameMode == MachinePlaysBlack ||
13649         gameMode == TwoMachinesPlay ||
13650         gameMode == EndOfGame) {
13651         i = forwardMostMove;
13652         while (i > currentMove) {
13653             SendToProgram("undo\n", &first);
13654             i--;
13655         }
13656         if(!adjustedClock) {
13657         whiteTimeRemaining = timeRemaining[0][currentMove];
13658         blackTimeRemaining = timeRemaining[1][currentMove];
13659         DisplayBothClocks();
13660         }
13661         if (whiteFlag || blackFlag) {
13662             whiteFlag = blackFlag = 0;
13663         }
13664         DisplayTitle("");
13665     }
13666
13667     gameMode = EditGame;
13668     ModeHighlight();
13669     SetGameInfo();
13670 }
13671
13672
13673 void
13674 EditPositionEvent()
13675 {
13676     if (gameMode == EditPosition) {
13677         EditGameEvent();
13678         return;
13679     }
13680
13681     EditGameEvent();
13682     if (gameMode != EditGame) return;
13683
13684     gameMode = EditPosition;
13685     ModeHighlight();
13686     SetGameInfo();
13687     if (currentMove > 0)
13688       CopyBoard(boards[0], boards[currentMove]);
13689
13690     blackPlaysFirst = !WhiteOnMove(currentMove);
13691     ResetClocks();
13692     currentMove = forwardMostMove = backwardMostMove = 0;
13693     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13694     DisplayMove(-1);
13695 }
13696
13697 void
13698 ExitAnalyzeMode()
13699 {
13700     /* [DM] icsEngineAnalyze - possible call from other functions */
13701     if (appData.icsEngineAnalyze) {
13702         appData.icsEngineAnalyze = FALSE;
13703
13704         DisplayMessage("",_("Close ICS engine analyze..."));
13705     }
13706     if (first.analysisSupport && first.analyzing) {
13707       SendToProgram("exit\n", &first);
13708       first.analyzing = FALSE;
13709     }
13710     thinkOutput[0] = NULLCHAR;
13711 }
13712
13713 void
13714 EditPositionDone(Boolean fakeRights)
13715 {
13716     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13717
13718     startedFromSetupPosition = TRUE;
13719     InitChessProgram(&first, FALSE);
13720     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13721       boards[0][EP_STATUS] = EP_NONE;
13722       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13723     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13724         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13725         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13726       } else boards[0][CASTLING][2] = NoRights;
13727     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13728         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13729         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13730       } else boards[0][CASTLING][5] = NoRights;
13731     }
13732     SendToProgram("force\n", &first);
13733     if (blackPlaysFirst) {
13734         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13735         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13736         currentMove = forwardMostMove = backwardMostMove = 1;
13737         CopyBoard(boards[1], boards[0]);
13738     } else {
13739         currentMove = forwardMostMove = backwardMostMove = 0;
13740     }
13741     SendBoard(&first, forwardMostMove);
13742     if (appData.debugMode) {
13743         fprintf(debugFP, "EditPosDone\n");
13744     }
13745     DisplayTitle("");
13746     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13747     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13748     gameMode = EditGame;
13749     ModeHighlight();
13750     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13751     ClearHighlights(); /* [AS] */
13752 }
13753
13754 /* Pause for `ms' milliseconds */
13755 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13756 void
13757 TimeDelay(ms)
13758      long ms;
13759 {
13760     TimeMark m1, m2;
13761
13762     GetTimeMark(&m1);
13763     do {
13764         GetTimeMark(&m2);
13765     } while (SubtractTimeMarks(&m2, &m1) < ms);
13766 }
13767
13768 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13769 void
13770 SendMultiLineToICS(buf)
13771      char *buf;
13772 {
13773     char temp[MSG_SIZ+1], *p;
13774     int len;
13775
13776     len = strlen(buf);
13777     if (len > MSG_SIZ)
13778       len = MSG_SIZ;
13779
13780     strncpy(temp, buf, len);
13781     temp[len] = 0;
13782
13783     p = temp;
13784     while (*p) {
13785         if (*p == '\n' || *p == '\r')
13786           *p = ' ';
13787         ++p;
13788     }
13789
13790     strcat(temp, "\n");
13791     SendToICS(temp);
13792     SendToPlayer(temp, strlen(temp));
13793 }
13794
13795 void
13796 SetWhiteToPlayEvent()
13797 {
13798     if (gameMode == EditPosition) {
13799         blackPlaysFirst = FALSE;
13800         DisplayBothClocks();    /* works because currentMove is 0 */
13801     } else if (gameMode == IcsExamining) {
13802         SendToICS(ics_prefix);
13803         SendToICS("tomove white\n");
13804     }
13805 }
13806
13807 void
13808 SetBlackToPlayEvent()
13809 {
13810     if (gameMode == EditPosition) {
13811         blackPlaysFirst = TRUE;
13812         currentMove = 1;        /* kludge */
13813         DisplayBothClocks();
13814         currentMove = 0;
13815     } else if (gameMode == IcsExamining) {
13816         SendToICS(ics_prefix);
13817         SendToICS("tomove black\n");
13818     }
13819 }
13820
13821 void
13822 EditPositionMenuEvent(selection, x, y)
13823      ChessSquare selection;
13824      int x, y;
13825 {
13826     char buf[MSG_SIZ];
13827     ChessSquare piece = boards[0][y][x];
13828
13829     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13830
13831     switch (selection) {
13832       case ClearBoard:
13833         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13834             SendToICS(ics_prefix);
13835             SendToICS("bsetup clear\n");
13836         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13837             SendToICS(ics_prefix);
13838             SendToICS("clearboard\n");
13839         } else {
13840             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13841                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13842                 for (y = 0; y < BOARD_HEIGHT; y++) {
13843                     if (gameMode == IcsExamining) {
13844                         if (boards[currentMove][y][x] != EmptySquare) {
13845                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13846                                     AAA + x, ONE + y);
13847                             SendToICS(buf);
13848                         }
13849                     } else {
13850                         boards[0][y][x] = p;
13851                     }
13852                 }
13853             }
13854         }
13855         if (gameMode == EditPosition) {
13856             DrawPosition(FALSE, boards[0]);
13857         }
13858         break;
13859
13860       case WhitePlay:
13861         SetWhiteToPlayEvent();
13862         break;
13863
13864       case BlackPlay:
13865         SetBlackToPlayEvent();
13866         break;
13867
13868       case EmptySquare:
13869         if (gameMode == IcsExamining) {
13870             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13871             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13872             SendToICS(buf);
13873         } else {
13874             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13875                 if(x == BOARD_LEFT-2) {
13876                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13877                     boards[0][y][1] = 0;
13878                 } else
13879                 if(x == BOARD_RGHT+1) {
13880                     if(y >= gameInfo.holdingsSize) break;
13881                     boards[0][y][BOARD_WIDTH-2] = 0;
13882                 } else break;
13883             }
13884             boards[0][y][x] = EmptySquare;
13885             DrawPosition(FALSE, boards[0]);
13886         }
13887         break;
13888
13889       case PromotePiece:
13890         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13891            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13892             selection = (ChessSquare) (PROMOTED piece);
13893         } else if(piece == EmptySquare) selection = WhiteSilver;
13894         else selection = (ChessSquare)((int)piece - 1);
13895         goto defaultlabel;
13896
13897       case DemotePiece:
13898         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13899            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13900             selection = (ChessSquare) (DEMOTED piece);
13901         } else if(piece == EmptySquare) selection = BlackSilver;
13902         else selection = (ChessSquare)((int)piece + 1);
13903         goto defaultlabel;
13904
13905       case WhiteQueen:
13906       case BlackQueen:
13907         if(gameInfo.variant == VariantShatranj ||
13908            gameInfo.variant == VariantXiangqi  ||
13909            gameInfo.variant == VariantCourier  ||
13910            gameInfo.variant == VariantMakruk     )
13911             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13912         goto defaultlabel;
13913
13914       case WhiteKing:
13915       case BlackKing:
13916         if(gameInfo.variant == VariantXiangqi)
13917             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13918         if(gameInfo.variant == VariantKnightmate)
13919             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13920       default:
13921         defaultlabel:
13922         if (gameMode == IcsExamining) {
13923             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13924             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13925                      PieceToChar(selection), AAA + x, ONE + y);
13926             SendToICS(buf);
13927         } else {
13928             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13929                 int n;
13930                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13931                     n = PieceToNumber(selection - BlackPawn);
13932                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13933                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13934                     boards[0][BOARD_HEIGHT-1-n][1]++;
13935                 } else
13936                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13937                     n = PieceToNumber(selection);
13938                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13939                     boards[0][n][BOARD_WIDTH-1] = selection;
13940                     boards[0][n][BOARD_WIDTH-2]++;
13941                 }
13942             } else
13943             boards[0][y][x] = selection;
13944             DrawPosition(TRUE, boards[0]);
13945         }
13946         break;
13947     }
13948 }
13949
13950
13951 void
13952 DropMenuEvent(selection, x, y)
13953      ChessSquare selection;
13954      int x, y;
13955 {
13956     ChessMove moveType;
13957
13958     switch (gameMode) {
13959       case IcsPlayingWhite:
13960       case MachinePlaysBlack:
13961         if (!WhiteOnMove(currentMove)) {
13962             DisplayMoveError(_("It is Black's turn"));
13963             return;
13964         }
13965         moveType = WhiteDrop;
13966         break;
13967       case IcsPlayingBlack:
13968       case MachinePlaysWhite:
13969         if (WhiteOnMove(currentMove)) {
13970             DisplayMoveError(_("It is White's turn"));
13971             return;
13972         }
13973         moveType = BlackDrop;
13974         break;
13975       case EditGame:
13976         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13977         break;
13978       default:
13979         return;
13980     }
13981
13982     if (moveType == BlackDrop && selection < BlackPawn) {
13983       selection = (ChessSquare) ((int) selection
13984                                  + (int) BlackPawn - (int) WhitePawn);
13985     }
13986     if (boards[currentMove][y][x] != EmptySquare) {
13987         DisplayMoveError(_("That square is occupied"));
13988         return;
13989     }
13990
13991     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13992 }
13993
13994 void
13995 AcceptEvent()
13996 {
13997     /* Accept a pending offer of any kind from opponent */
13998
13999     if (appData.icsActive) {
14000         SendToICS(ics_prefix);
14001         SendToICS("accept\n");
14002     } else if (cmailMsgLoaded) {
14003         if (currentMove == cmailOldMove &&
14004             commentList[cmailOldMove] != NULL &&
14005             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14006                    "Black offers a draw" : "White offers a draw")) {
14007             TruncateGame();
14008             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14009             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14010         } else {
14011             DisplayError(_("There is no pending offer on this move"), 0);
14012             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14013         }
14014     } else {
14015         /* Not used for offers from chess program */
14016     }
14017 }
14018
14019 void
14020 DeclineEvent()
14021 {
14022     /* Decline a pending offer of any kind from opponent */
14023
14024     if (appData.icsActive) {
14025         SendToICS(ics_prefix);
14026         SendToICS("decline\n");
14027     } else if (cmailMsgLoaded) {
14028         if (currentMove == cmailOldMove &&
14029             commentList[cmailOldMove] != NULL &&
14030             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14031                    "Black offers a draw" : "White offers a draw")) {
14032 #ifdef NOTDEF
14033             AppendComment(cmailOldMove, "Draw declined", TRUE);
14034             DisplayComment(cmailOldMove - 1, "Draw declined");
14035 #endif /*NOTDEF*/
14036         } else {
14037             DisplayError(_("There is no pending offer on this move"), 0);
14038         }
14039     } else {
14040         /* Not used for offers from chess program */
14041     }
14042 }
14043
14044 void
14045 RematchEvent()
14046 {
14047     /* Issue ICS rematch command */
14048     if (appData.icsActive) {
14049         SendToICS(ics_prefix);
14050         SendToICS("rematch\n");
14051     }
14052 }
14053
14054 void
14055 CallFlagEvent()
14056 {
14057     /* Call your opponent's flag (claim a win on time) */
14058     if (appData.icsActive) {
14059         SendToICS(ics_prefix);
14060         SendToICS("flag\n");
14061     } else {
14062         switch (gameMode) {
14063           default:
14064             return;
14065           case MachinePlaysWhite:
14066             if (whiteFlag) {
14067                 if (blackFlag)
14068                   GameEnds(GameIsDrawn, "Both players ran out of time",
14069                            GE_PLAYER);
14070                 else
14071                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14072             } else {
14073                 DisplayError(_("Your opponent is not out of time"), 0);
14074             }
14075             break;
14076           case MachinePlaysBlack:
14077             if (blackFlag) {
14078                 if (whiteFlag)
14079                   GameEnds(GameIsDrawn, "Both players ran out of time",
14080                            GE_PLAYER);
14081                 else
14082                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14083             } else {
14084                 DisplayError(_("Your opponent is not out of time"), 0);
14085             }
14086             break;
14087         }
14088     }
14089 }
14090
14091 void
14092 ClockClick(int which)
14093 {       // [HGM] code moved to back-end from winboard.c
14094         if(which) { // black clock
14095           if (gameMode == EditPosition || gameMode == IcsExamining) {
14096             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14097             SetBlackToPlayEvent();
14098           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14099           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14100           } else if (shiftKey) {
14101             AdjustClock(which, -1);
14102           } else if (gameMode == IcsPlayingWhite ||
14103                      gameMode == MachinePlaysBlack) {
14104             CallFlagEvent();
14105           }
14106         } else { // white clock
14107           if (gameMode == EditPosition || gameMode == IcsExamining) {
14108             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14109             SetWhiteToPlayEvent();
14110           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14111           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14112           } else if (shiftKey) {
14113             AdjustClock(which, -1);
14114           } else if (gameMode == IcsPlayingBlack ||
14115                    gameMode == MachinePlaysWhite) {
14116             CallFlagEvent();
14117           }
14118         }
14119 }
14120
14121 void
14122 DrawEvent()
14123 {
14124     /* Offer draw or accept pending draw offer from opponent */
14125
14126     if (appData.icsActive) {
14127         /* Note: tournament rules require draw offers to be
14128            made after you make your move but before you punch
14129            your clock.  Currently ICS doesn't let you do that;
14130            instead, you immediately punch your clock after making
14131            a move, but you can offer a draw at any time. */
14132
14133         SendToICS(ics_prefix);
14134         SendToICS("draw\n");
14135         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14136     } else if (cmailMsgLoaded) {
14137         if (currentMove == cmailOldMove &&
14138             commentList[cmailOldMove] != NULL &&
14139             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14140                    "Black offers a draw" : "White offers a draw")) {
14141             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14142             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14143         } else if (currentMove == cmailOldMove + 1) {
14144             char *offer = WhiteOnMove(cmailOldMove) ?
14145               "White offers a draw" : "Black offers a draw";
14146             AppendComment(currentMove, offer, TRUE);
14147             DisplayComment(currentMove - 1, offer);
14148             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14149         } else {
14150             DisplayError(_("You must make your move before offering a draw"), 0);
14151             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14152         }
14153     } else if (first.offeredDraw) {
14154         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14155     } else {
14156         if (first.sendDrawOffers) {
14157             SendToProgram("draw\n", &first);
14158             userOfferedDraw = TRUE;
14159         }
14160     }
14161 }
14162
14163 void
14164 AdjournEvent()
14165 {
14166     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14167
14168     if (appData.icsActive) {
14169         SendToICS(ics_prefix);
14170         SendToICS("adjourn\n");
14171     } else {
14172         /* Currently GNU Chess doesn't offer or accept Adjourns */
14173     }
14174 }
14175
14176
14177 void
14178 AbortEvent()
14179 {
14180     /* Offer Abort or accept pending Abort offer from opponent */
14181
14182     if (appData.icsActive) {
14183         SendToICS(ics_prefix);
14184         SendToICS("abort\n");
14185     } else {
14186         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14187     }
14188 }
14189
14190 void
14191 ResignEvent()
14192 {
14193     /* Resign.  You can do this even if it's not your turn. */
14194
14195     if (appData.icsActive) {
14196         SendToICS(ics_prefix);
14197         SendToICS("resign\n");
14198     } else {
14199         switch (gameMode) {
14200           case MachinePlaysWhite:
14201             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14202             break;
14203           case MachinePlaysBlack:
14204             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14205             break;
14206           case EditGame:
14207             if (cmailMsgLoaded) {
14208                 TruncateGame();
14209                 if (WhiteOnMove(cmailOldMove)) {
14210                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14211                 } else {
14212                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14213                 }
14214                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14215             }
14216             break;
14217           default:
14218             break;
14219         }
14220     }
14221 }
14222
14223
14224 void
14225 StopObservingEvent()
14226 {
14227     /* Stop observing current games */
14228     SendToICS(ics_prefix);
14229     SendToICS("unobserve\n");
14230 }
14231
14232 void
14233 StopExaminingEvent()
14234 {
14235     /* Stop observing current game */
14236     SendToICS(ics_prefix);
14237     SendToICS("unexamine\n");
14238 }
14239
14240 void
14241 ForwardInner(target)
14242      int target;
14243 {
14244     int limit;
14245
14246     if (appData.debugMode)
14247         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14248                 target, currentMove, forwardMostMove);
14249
14250     if (gameMode == EditPosition)
14251       return;
14252
14253     MarkTargetSquares(1);
14254
14255     if (gameMode == PlayFromGameFile && !pausing)
14256       PauseEvent();
14257
14258     if (gameMode == IcsExamining && pausing)
14259       limit = pauseExamForwardMostMove;
14260     else
14261       limit = forwardMostMove;
14262
14263     if (target > limit) target = limit;
14264
14265     if (target > 0 && moveList[target - 1][0]) {
14266         int fromX, fromY, toX, toY;
14267         toX = moveList[target - 1][2] - AAA;
14268         toY = moveList[target - 1][3] - ONE;
14269         if (moveList[target - 1][1] == '@') {
14270             if (appData.highlightLastMove) {
14271                 SetHighlights(-1, -1, toX, toY);
14272             }
14273         } else {
14274             fromX = moveList[target - 1][0] - AAA;
14275             fromY = moveList[target - 1][1] - ONE;
14276             if (target == currentMove + 1) {
14277                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14278             }
14279             if (appData.highlightLastMove) {
14280                 SetHighlights(fromX, fromY, toX, toY);
14281             }
14282         }
14283     }
14284     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14285         gameMode == Training || gameMode == PlayFromGameFile ||
14286         gameMode == AnalyzeFile) {
14287         while (currentMove < target) {
14288             SendMoveToProgram(currentMove++, &first);
14289         }
14290     } else {
14291         currentMove = target;
14292     }
14293
14294     if (gameMode == EditGame || gameMode == EndOfGame) {
14295         whiteTimeRemaining = timeRemaining[0][currentMove];
14296         blackTimeRemaining = timeRemaining[1][currentMove];
14297     }
14298     DisplayBothClocks();
14299     DisplayMove(currentMove - 1);
14300     DrawPosition(FALSE, boards[currentMove]);
14301     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14302     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14303         DisplayComment(currentMove - 1, commentList[currentMove]);
14304     }
14305 }
14306
14307
14308 void
14309 ForwardEvent()
14310 {
14311     if (gameMode == IcsExamining && !pausing) {
14312         SendToICS(ics_prefix);
14313         SendToICS("forward\n");
14314     } else {
14315         ForwardInner(currentMove + 1);
14316     }
14317 }
14318
14319 void
14320 ToEndEvent()
14321 {
14322     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14323         /* to optimze, we temporarily turn off analysis mode while we feed
14324          * the remaining moves to the engine. Otherwise we get analysis output
14325          * after each move.
14326          */
14327         if (first.analysisSupport) {
14328           SendToProgram("exit\nforce\n", &first);
14329           first.analyzing = FALSE;
14330         }
14331     }
14332
14333     if (gameMode == IcsExamining && !pausing) {
14334         SendToICS(ics_prefix);
14335         SendToICS("forward 999999\n");
14336     } else {
14337         ForwardInner(forwardMostMove);
14338     }
14339
14340     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14341         /* we have fed all the moves, so reactivate analysis mode */
14342         SendToProgram("analyze\n", &first);
14343         first.analyzing = TRUE;
14344         /*first.maybeThinking = TRUE;*/
14345         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14346     }
14347 }
14348
14349 void
14350 BackwardInner(target)
14351      int target;
14352 {
14353     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14354
14355     if (appData.debugMode)
14356         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14357                 target, currentMove, forwardMostMove);
14358
14359     if (gameMode == EditPosition) return;
14360     MarkTargetSquares(1);
14361     if (currentMove <= backwardMostMove) {
14362         ClearHighlights();
14363         DrawPosition(full_redraw, boards[currentMove]);
14364         return;
14365     }
14366     if (gameMode == PlayFromGameFile && !pausing)
14367       PauseEvent();
14368
14369     if (moveList[target][0]) {
14370         int fromX, fromY, toX, toY;
14371         toX = moveList[target][2] - AAA;
14372         toY = moveList[target][3] - ONE;
14373         if (moveList[target][1] == '@') {
14374             if (appData.highlightLastMove) {
14375                 SetHighlights(-1, -1, toX, toY);
14376             }
14377         } else {
14378             fromX = moveList[target][0] - AAA;
14379             fromY = moveList[target][1] - ONE;
14380             if (target == currentMove - 1) {
14381                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14382             }
14383             if (appData.highlightLastMove) {
14384                 SetHighlights(fromX, fromY, toX, toY);
14385             }
14386         }
14387     }
14388     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14389         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14390         while (currentMove > target) {
14391             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14392                 // null move cannot be undone. Reload program with move history before it.
14393                 int i;
14394                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14395                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14396                 }
14397                 SendBoard(&first, i); 
14398                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14399                 break;
14400             }
14401             SendToProgram("undo\n", &first);
14402             currentMove--;
14403         }
14404     } else {
14405         currentMove = target;
14406     }
14407
14408     if (gameMode == EditGame || gameMode == EndOfGame) {
14409         whiteTimeRemaining = timeRemaining[0][currentMove];
14410         blackTimeRemaining = timeRemaining[1][currentMove];
14411     }
14412     DisplayBothClocks();
14413     DisplayMove(currentMove - 1);
14414     DrawPosition(full_redraw, boards[currentMove]);
14415     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14416     // [HGM] PV info: routine tests if comment empty
14417     DisplayComment(currentMove - 1, commentList[currentMove]);
14418 }
14419
14420 void
14421 BackwardEvent()
14422 {
14423     if (gameMode == IcsExamining && !pausing) {
14424         SendToICS(ics_prefix);
14425         SendToICS("backward\n");
14426     } else {
14427         BackwardInner(currentMove - 1);
14428     }
14429 }
14430
14431 void
14432 ToStartEvent()
14433 {
14434     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14435         /* to optimize, we temporarily turn off analysis mode while we undo
14436          * all the moves. Otherwise we get analysis output after each undo.
14437          */
14438         if (first.analysisSupport) {
14439           SendToProgram("exit\nforce\n", &first);
14440           first.analyzing = FALSE;
14441         }
14442     }
14443
14444     if (gameMode == IcsExamining && !pausing) {
14445         SendToICS(ics_prefix);
14446         SendToICS("backward 999999\n");
14447     } else {
14448         BackwardInner(backwardMostMove);
14449     }
14450
14451     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14452         /* we have fed all the moves, so reactivate analysis mode */
14453         SendToProgram("analyze\n", &first);
14454         first.analyzing = TRUE;
14455         /*first.maybeThinking = TRUE;*/
14456         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14457     }
14458 }
14459
14460 void
14461 ToNrEvent(int to)
14462 {
14463   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14464   if (to >= forwardMostMove) to = forwardMostMove;
14465   if (to <= backwardMostMove) to = backwardMostMove;
14466   if (to < currentMove) {
14467     BackwardInner(to);
14468   } else {
14469     ForwardInner(to);
14470   }
14471 }
14472
14473 void
14474 RevertEvent(Boolean annotate)
14475 {
14476     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14477         return;
14478     }
14479     if (gameMode != IcsExamining) {
14480         DisplayError(_("You are not examining a game"), 0);
14481         return;
14482     }
14483     if (pausing) {
14484         DisplayError(_("You can't revert while pausing"), 0);
14485         return;
14486     }
14487     SendToICS(ics_prefix);
14488     SendToICS("revert\n");
14489 }
14490
14491 void
14492 RetractMoveEvent()
14493 {
14494     switch (gameMode) {
14495       case MachinePlaysWhite:
14496       case MachinePlaysBlack:
14497         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14498             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14499             return;
14500         }
14501         if (forwardMostMove < 2) return;
14502         currentMove = forwardMostMove = forwardMostMove - 2;
14503         whiteTimeRemaining = timeRemaining[0][currentMove];
14504         blackTimeRemaining = timeRemaining[1][currentMove];
14505         DisplayBothClocks();
14506         DisplayMove(currentMove - 1);
14507         ClearHighlights();/*!! could figure this out*/
14508         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14509         SendToProgram("remove\n", &first);
14510         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14511         break;
14512
14513       case BeginningOfGame:
14514       default:
14515         break;
14516
14517       case IcsPlayingWhite:
14518       case IcsPlayingBlack:
14519         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14520             SendToICS(ics_prefix);
14521             SendToICS("takeback 2\n");
14522         } else {
14523             SendToICS(ics_prefix);
14524             SendToICS("takeback 1\n");
14525         }
14526         break;
14527     }
14528 }
14529
14530 void
14531 MoveNowEvent()
14532 {
14533     ChessProgramState *cps;
14534
14535     switch (gameMode) {
14536       case MachinePlaysWhite:
14537         if (!WhiteOnMove(forwardMostMove)) {
14538             DisplayError(_("It is your turn"), 0);
14539             return;
14540         }
14541         cps = &first;
14542         break;
14543       case MachinePlaysBlack:
14544         if (WhiteOnMove(forwardMostMove)) {
14545             DisplayError(_("It is your turn"), 0);
14546             return;
14547         }
14548         cps = &first;
14549         break;
14550       case TwoMachinesPlay:
14551         if (WhiteOnMove(forwardMostMove) ==
14552             (first.twoMachinesColor[0] == 'w')) {
14553             cps = &first;
14554         } else {
14555             cps = &second;
14556         }
14557         break;
14558       case BeginningOfGame:
14559       default:
14560         return;
14561     }
14562     SendToProgram("?\n", cps);
14563 }
14564
14565 void
14566 TruncateGameEvent()
14567 {
14568     EditGameEvent();
14569     if (gameMode != EditGame) return;
14570     TruncateGame();
14571 }
14572
14573 void
14574 TruncateGame()
14575 {
14576     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14577     if (forwardMostMove > currentMove) {
14578         if (gameInfo.resultDetails != NULL) {
14579             free(gameInfo.resultDetails);
14580             gameInfo.resultDetails = NULL;
14581             gameInfo.result = GameUnfinished;
14582         }
14583         forwardMostMove = currentMove;
14584         HistorySet(parseList, backwardMostMove, forwardMostMove,
14585                    currentMove-1);
14586     }
14587 }
14588
14589 void
14590 HintEvent()
14591 {
14592     if (appData.noChessProgram) return;
14593     switch (gameMode) {
14594       case MachinePlaysWhite:
14595         if (WhiteOnMove(forwardMostMove)) {
14596             DisplayError(_("Wait until your turn"), 0);
14597             return;
14598         }
14599         break;
14600       case BeginningOfGame:
14601       case MachinePlaysBlack:
14602         if (!WhiteOnMove(forwardMostMove)) {
14603             DisplayError(_("Wait until your turn"), 0);
14604             return;
14605         }
14606         break;
14607       default:
14608         DisplayError(_("No hint available"), 0);
14609         return;
14610     }
14611     SendToProgram("hint\n", &first);
14612     hintRequested = TRUE;
14613 }
14614
14615 void
14616 BookEvent()
14617 {
14618     if (appData.noChessProgram) return;
14619     switch (gameMode) {
14620       case MachinePlaysWhite:
14621         if (WhiteOnMove(forwardMostMove)) {
14622             DisplayError(_("Wait until your turn"), 0);
14623             return;
14624         }
14625         break;
14626       case BeginningOfGame:
14627       case MachinePlaysBlack:
14628         if (!WhiteOnMove(forwardMostMove)) {
14629             DisplayError(_("Wait until your turn"), 0);
14630             return;
14631         }
14632         break;
14633       case EditPosition:
14634         EditPositionDone(TRUE);
14635         break;
14636       case TwoMachinesPlay:
14637         return;
14638       default:
14639         break;
14640     }
14641     SendToProgram("bk\n", &first);
14642     bookOutput[0] = NULLCHAR;
14643     bookRequested = TRUE;
14644 }
14645
14646 void
14647 AboutGameEvent()
14648 {
14649     char *tags = PGNTags(&gameInfo);
14650     TagsPopUp(tags, CmailMsg());
14651     free(tags);
14652 }
14653
14654 /* end button procedures */
14655
14656 void
14657 PrintPosition(fp, move)
14658      FILE *fp;
14659      int move;
14660 {
14661     int i, j;
14662
14663     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14664         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14665             char c = PieceToChar(boards[move][i][j]);
14666             fputc(c == 'x' ? '.' : c, fp);
14667             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14668         }
14669     }
14670     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14671       fprintf(fp, "white to play\n");
14672     else
14673       fprintf(fp, "black to play\n");
14674 }
14675
14676 void
14677 PrintOpponents(fp)
14678      FILE *fp;
14679 {
14680     if (gameInfo.white != NULL) {
14681         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14682     } else {
14683         fprintf(fp, "\n");
14684     }
14685 }
14686
14687 /* Find last component of program's own name, using some heuristics */
14688 void
14689 TidyProgramName(prog, host, buf)
14690      char *prog, *host, buf[MSG_SIZ];
14691 {
14692     char *p, *q;
14693     int local = (strcmp(host, "localhost") == 0);
14694     while (!local && (p = strchr(prog, ';')) != NULL) {
14695         p++;
14696         while (*p == ' ') p++;
14697         prog = p;
14698     }
14699     if (*prog == '"' || *prog == '\'') {
14700         q = strchr(prog + 1, *prog);
14701     } else {
14702         q = strchr(prog, ' ');
14703     }
14704     if (q == NULL) q = prog + strlen(prog);
14705     p = q;
14706     while (p >= prog && *p != '/' && *p != '\\') p--;
14707     p++;
14708     if(p == prog && *p == '"') p++;
14709     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14710     memcpy(buf, p, q - p);
14711     buf[q - p] = NULLCHAR;
14712     if (!local) {
14713         strcat(buf, "@");
14714         strcat(buf, host);
14715     }
14716 }
14717
14718 char *
14719 TimeControlTagValue()
14720 {
14721     char buf[MSG_SIZ];
14722     if (!appData.clockMode) {
14723       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14724     } else if (movesPerSession > 0) {
14725       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14726     } else if (timeIncrement == 0) {
14727       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14728     } else {
14729       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14730     }
14731     return StrSave(buf);
14732 }
14733
14734 void
14735 SetGameInfo()
14736 {
14737     /* This routine is used only for certain modes */
14738     VariantClass v = gameInfo.variant;
14739     ChessMove r = GameUnfinished;
14740     char *p = NULL;
14741
14742     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14743         r = gameInfo.result;
14744         p = gameInfo.resultDetails;
14745         gameInfo.resultDetails = NULL;
14746     }
14747     ClearGameInfo(&gameInfo);
14748     gameInfo.variant = v;
14749
14750     switch (gameMode) {
14751       case MachinePlaysWhite:
14752         gameInfo.event = StrSave( appData.pgnEventHeader );
14753         gameInfo.site = StrSave(HostName());
14754         gameInfo.date = PGNDate();
14755         gameInfo.round = StrSave("-");
14756         gameInfo.white = StrSave(first.tidy);
14757         gameInfo.black = StrSave(UserName());
14758         gameInfo.timeControl = TimeControlTagValue();
14759         break;
14760
14761       case MachinePlaysBlack:
14762         gameInfo.event = StrSave( appData.pgnEventHeader );
14763         gameInfo.site = StrSave(HostName());
14764         gameInfo.date = PGNDate();
14765         gameInfo.round = StrSave("-");
14766         gameInfo.white = StrSave(UserName());
14767         gameInfo.black = StrSave(first.tidy);
14768         gameInfo.timeControl = TimeControlTagValue();
14769         break;
14770
14771       case TwoMachinesPlay:
14772         gameInfo.event = StrSave( appData.pgnEventHeader );
14773         gameInfo.site = StrSave(HostName());
14774         gameInfo.date = PGNDate();
14775         if (roundNr > 0) {
14776             char buf[MSG_SIZ];
14777             snprintf(buf, MSG_SIZ, "%d", roundNr);
14778             gameInfo.round = StrSave(buf);
14779         } else {
14780             gameInfo.round = StrSave("-");
14781         }
14782         if (first.twoMachinesColor[0] == 'w') {
14783             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14784             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14785         } else {
14786             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14787             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14788         }
14789         gameInfo.timeControl = TimeControlTagValue();
14790         break;
14791
14792       case EditGame:
14793         gameInfo.event = StrSave("Edited game");
14794         gameInfo.site = StrSave(HostName());
14795         gameInfo.date = PGNDate();
14796         gameInfo.round = StrSave("-");
14797         gameInfo.white = StrSave("-");
14798         gameInfo.black = StrSave("-");
14799         gameInfo.result = r;
14800         gameInfo.resultDetails = p;
14801         break;
14802
14803       case EditPosition:
14804         gameInfo.event = StrSave("Edited position");
14805         gameInfo.site = StrSave(HostName());
14806         gameInfo.date = PGNDate();
14807         gameInfo.round = StrSave("-");
14808         gameInfo.white = StrSave("-");
14809         gameInfo.black = StrSave("-");
14810         break;
14811
14812       case IcsPlayingWhite:
14813       case IcsPlayingBlack:
14814       case IcsObserving:
14815       case IcsExamining:
14816         break;
14817
14818       case PlayFromGameFile:
14819         gameInfo.event = StrSave("Game from non-PGN file");
14820         gameInfo.site = StrSave(HostName());
14821         gameInfo.date = PGNDate();
14822         gameInfo.round = StrSave("-");
14823         gameInfo.white = StrSave("?");
14824         gameInfo.black = StrSave("?");
14825         break;
14826
14827       default:
14828         break;
14829     }
14830 }
14831
14832 void
14833 ReplaceComment(index, text)
14834      int index;
14835      char *text;
14836 {
14837     int len;
14838     char *p;
14839     float score;
14840
14841     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14842        pvInfoList[index-1].depth == len &&
14843        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14844        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14845     while (*text == '\n') text++;
14846     len = strlen(text);
14847     while (len > 0 && text[len - 1] == '\n') len--;
14848
14849     if (commentList[index] != NULL)
14850       free(commentList[index]);
14851
14852     if (len == 0) {
14853         commentList[index] = NULL;
14854         return;
14855     }
14856   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14857       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14858       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14859     commentList[index] = (char *) malloc(len + 2);
14860     strncpy(commentList[index], text, len);
14861     commentList[index][len] = '\n';
14862     commentList[index][len + 1] = NULLCHAR;
14863   } else {
14864     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14865     char *p;
14866     commentList[index] = (char *) malloc(len + 7);
14867     safeStrCpy(commentList[index], "{\n", 3);
14868     safeStrCpy(commentList[index]+2, text, len+1);
14869     commentList[index][len+2] = NULLCHAR;
14870     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14871     strcat(commentList[index], "\n}\n");
14872   }
14873 }
14874
14875 void
14876 CrushCRs(text)
14877      char *text;
14878 {
14879   char *p = text;
14880   char *q = text;
14881   char ch;
14882
14883   do {
14884     ch = *p++;
14885     if (ch == '\r') continue;
14886     *q++ = ch;
14887   } while (ch != '\0');
14888 }
14889
14890 void
14891 AppendComment(index, text, addBraces)
14892      int index;
14893      char *text;
14894      Boolean addBraces; // [HGM] braces: tells if we should add {}
14895 {
14896     int oldlen, len;
14897     char *old;
14898
14899 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14900     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14901
14902     CrushCRs(text);
14903     while (*text == '\n') text++;
14904     len = strlen(text);
14905     while (len > 0 && text[len - 1] == '\n') len--;
14906     text[len] = NULLCHAR;
14907
14908     if (len == 0) return;
14909
14910     if (commentList[index] != NULL) {
14911       Boolean addClosingBrace = addBraces;
14912         old = commentList[index];
14913         oldlen = strlen(old);
14914         while(commentList[index][oldlen-1] ==  '\n')
14915           commentList[index][--oldlen] = NULLCHAR;
14916         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14917         safeStrCpy(commentList[index], old, oldlen + len + 6);
14918         free(old);
14919         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14920         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14921           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14922           while (*text == '\n') { text++; len--; }
14923           commentList[index][--oldlen] = NULLCHAR;
14924       }
14925         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14926         else          strcat(commentList[index], "\n");
14927         strcat(commentList[index], text);
14928         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14929         else          strcat(commentList[index], "\n");
14930     } else {
14931         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14932         if(addBraces)
14933           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14934         else commentList[index][0] = NULLCHAR;
14935         strcat(commentList[index], text);
14936         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14937         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14938     }
14939 }
14940
14941 static char * FindStr( char * text, char * sub_text )
14942 {
14943     char * result = strstr( text, sub_text );
14944
14945     if( result != NULL ) {
14946         result += strlen( sub_text );
14947     }
14948
14949     return result;
14950 }
14951
14952 /* [AS] Try to extract PV info from PGN comment */
14953 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14954 char *GetInfoFromComment( int index, char * text )
14955 {
14956     char * sep = text, *p;
14957
14958     if( text != NULL && index > 0 ) {
14959         int score = 0;
14960         int depth = 0;
14961         int time = -1, sec = 0, deci;
14962         char * s_eval = FindStr( text, "[%eval " );
14963         char * s_emt = FindStr( text, "[%emt " );
14964
14965         if( s_eval != NULL || s_emt != NULL ) {
14966             /* New style */
14967             char delim;
14968
14969             if( s_eval != NULL ) {
14970                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14971                     return text;
14972                 }
14973
14974                 if( delim != ']' ) {
14975                     return text;
14976                 }
14977             }
14978
14979             if( s_emt != NULL ) {
14980             }
14981                 return text;
14982         }
14983         else {
14984             /* We expect something like: [+|-]nnn.nn/dd */
14985             int score_lo = 0;
14986
14987             if(*text != '{') return text; // [HGM] braces: must be normal comment
14988
14989             sep = strchr( text, '/' );
14990             if( sep == NULL || sep < (text+4) ) {
14991                 return text;
14992             }
14993
14994             p = text;
14995             if(p[1] == '(') { // comment starts with PV
14996                p = strchr(p, ')'); // locate end of PV
14997                if(p == NULL || sep < p+5) return text;
14998                // at this point we have something like "{(.*) +0.23/6 ..."
14999                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15000                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15001                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15002             }
15003             time = -1; sec = -1; deci = -1;
15004             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15005                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15006                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15007                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15008                 return text;
15009             }
15010
15011             if( score_lo < 0 || score_lo >= 100 ) {
15012                 return text;
15013             }
15014
15015             if(sec >= 0) time = 600*time + 10*sec; else
15016             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15017
15018             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15019
15020             /* [HGM] PV time: now locate end of PV info */
15021             while( *++sep >= '0' && *sep <= '9'); // strip depth
15022             if(time >= 0)
15023             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15024             if(sec >= 0)
15025             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15026             if(deci >= 0)
15027             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15028             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15029         }
15030
15031         if( depth <= 0 ) {
15032             return text;
15033         }
15034
15035         if( time < 0 ) {
15036             time = -1;
15037         }
15038
15039         pvInfoList[index-1].depth = depth;
15040         pvInfoList[index-1].score = score;
15041         pvInfoList[index-1].time  = 10*time; // centi-sec
15042         if(*sep == '}') *sep = 0; else *--sep = '{';
15043         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15044     }
15045     return sep;
15046 }
15047
15048 void
15049 SendToProgram(message, cps)
15050      char *message;
15051      ChessProgramState *cps;
15052 {
15053     int count, outCount, error;
15054     char buf[MSG_SIZ];
15055
15056     if (cps->pr == NoProc) return;
15057     Attention(cps);
15058
15059     if (appData.debugMode) {
15060         TimeMark now;
15061         GetTimeMark(&now);
15062         fprintf(debugFP, "%ld >%-6s: %s",
15063                 SubtractTimeMarks(&now, &programStartTime),
15064                 cps->which, message);
15065     }
15066
15067     count = strlen(message);
15068     outCount = OutputToProcess(cps->pr, message, count, &error);
15069     if (outCount < count && !exiting
15070                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15071       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15072       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15073         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15074             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15075                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15076                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15077                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15078             } else {
15079                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15080                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15081                 gameInfo.result = res;
15082             }
15083             gameInfo.resultDetails = StrSave(buf);
15084         }
15085         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15086         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15087     }
15088 }
15089
15090 void
15091 ReceiveFromProgram(isr, closure, message, count, error)
15092      InputSourceRef isr;
15093      VOIDSTAR closure;
15094      char *message;
15095      int count;
15096      int error;
15097 {
15098     char *end_str;
15099     char buf[MSG_SIZ];
15100     ChessProgramState *cps = (ChessProgramState *)closure;
15101
15102     if (isr != cps->isr) return; /* Killed intentionally */
15103     if (count <= 0) {
15104         if (count == 0) {
15105             RemoveInputSource(cps->isr);
15106             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15107             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15108                     _(cps->which), cps->program);
15109         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15110                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15111                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15112                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15113                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15114                 } else {
15115                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15116                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15117                     gameInfo.result = res;
15118                 }
15119                 gameInfo.resultDetails = StrSave(buf);
15120             }
15121             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15122             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15123         } else {
15124             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15125                     _(cps->which), cps->program);
15126             RemoveInputSource(cps->isr);
15127
15128             /* [AS] Program is misbehaving badly... kill it */
15129             if( count == -2 ) {
15130                 DestroyChildProcess( cps->pr, 9 );
15131                 cps->pr = NoProc;
15132             }
15133
15134             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15135         }
15136         return;
15137     }
15138
15139     if ((end_str = strchr(message, '\r')) != NULL)
15140       *end_str = NULLCHAR;
15141     if ((end_str = strchr(message, '\n')) != NULL)
15142       *end_str = NULLCHAR;
15143
15144     if (appData.debugMode) {
15145         TimeMark now; int print = 1;
15146         char *quote = ""; char c; int i;
15147
15148         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15149                 char start = message[0];
15150                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15151                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15152                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15153                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15154                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15155                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15156                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15157                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15158                    sscanf(message, "hint: %c", &c)!=1 && 
15159                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15160                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15161                     print = (appData.engineComments >= 2);
15162                 }
15163                 message[0] = start; // restore original message
15164         }
15165         if(print) {
15166                 GetTimeMark(&now);
15167                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15168                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15169                         quote,
15170                         message);
15171         }
15172     }
15173
15174     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15175     if (appData.icsEngineAnalyze) {
15176         if (strstr(message, "whisper") != NULL ||
15177              strstr(message, "kibitz") != NULL ||
15178             strstr(message, "tellics") != NULL) return;
15179     }
15180
15181     HandleMachineMove(message, cps);
15182 }
15183
15184
15185 void
15186 SendTimeControl(cps, mps, tc, inc, sd, st)
15187      ChessProgramState *cps;
15188      int mps, inc, sd, st;
15189      long tc;
15190 {
15191     char buf[MSG_SIZ];
15192     int seconds;
15193
15194     if( timeControl_2 > 0 ) {
15195         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15196             tc = timeControl_2;
15197         }
15198     }
15199     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15200     inc /= cps->timeOdds;
15201     st  /= cps->timeOdds;
15202
15203     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15204
15205     if (st > 0) {
15206       /* Set exact time per move, normally using st command */
15207       if (cps->stKludge) {
15208         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15209         seconds = st % 60;
15210         if (seconds == 0) {
15211           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15212         } else {
15213           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15214         }
15215       } else {
15216         snprintf(buf, MSG_SIZ, "st %d\n", st);
15217       }
15218     } else {
15219       /* Set conventional or incremental time control, using level command */
15220       if (seconds == 0) {
15221         /* Note old gnuchess bug -- minutes:seconds used to not work.
15222            Fixed in later versions, but still avoid :seconds
15223            when seconds is 0. */
15224         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15225       } else {
15226         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15227                  seconds, inc/1000.);
15228       }
15229     }
15230     SendToProgram(buf, cps);
15231
15232     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15233     /* Orthogonally, limit search to given depth */
15234     if (sd > 0) {
15235       if (cps->sdKludge) {
15236         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15237       } else {
15238         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15239       }
15240       SendToProgram(buf, cps);
15241     }
15242
15243     if(cps->nps >= 0) { /* [HGM] nps */
15244         if(cps->supportsNPS == FALSE)
15245           cps->nps = -1; // don't use if engine explicitly says not supported!
15246         else {
15247           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15248           SendToProgram(buf, cps);
15249         }
15250     }
15251 }
15252
15253 ChessProgramState *WhitePlayer()
15254 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15255 {
15256     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15257        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15258         return &second;
15259     return &first;
15260 }
15261
15262 void
15263 SendTimeRemaining(cps, machineWhite)
15264      ChessProgramState *cps;
15265      int /*boolean*/ machineWhite;
15266 {
15267     char message[MSG_SIZ];
15268     long time, otime;
15269
15270     /* Note: this routine must be called when the clocks are stopped
15271        or when they have *just* been set or switched; otherwise
15272        it will be off by the time since the current tick started.
15273     */
15274     if (machineWhite) {
15275         time = whiteTimeRemaining / 10;
15276         otime = blackTimeRemaining / 10;
15277     } else {
15278         time = blackTimeRemaining / 10;
15279         otime = whiteTimeRemaining / 10;
15280     }
15281     /* [HGM] translate opponent's time by time-odds factor */
15282     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15283     if (appData.debugMode) {
15284         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15285     }
15286
15287     if (time <= 0) time = 1;
15288     if (otime <= 0) otime = 1;
15289
15290     snprintf(message, MSG_SIZ, "time %ld\n", time);
15291     SendToProgram(message, cps);
15292
15293     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15294     SendToProgram(message, cps);
15295 }
15296
15297 int
15298 BoolFeature(p, name, loc, cps)
15299      char **p;
15300      char *name;
15301      int *loc;
15302      ChessProgramState *cps;
15303 {
15304   char buf[MSG_SIZ];
15305   int len = strlen(name);
15306   int val;
15307
15308   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15309     (*p) += len + 1;
15310     sscanf(*p, "%d", &val);
15311     *loc = (val != 0);
15312     while (**p && **p != ' ')
15313       (*p)++;
15314     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15315     SendToProgram(buf, cps);
15316     return TRUE;
15317   }
15318   return FALSE;
15319 }
15320
15321 int
15322 IntFeature(p, name, loc, cps)
15323      char **p;
15324      char *name;
15325      int *loc;
15326      ChessProgramState *cps;
15327 {
15328   char buf[MSG_SIZ];
15329   int len = strlen(name);
15330   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15331     (*p) += len + 1;
15332     sscanf(*p, "%d", loc);
15333     while (**p && **p != ' ') (*p)++;
15334     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15335     SendToProgram(buf, cps);
15336     return TRUE;
15337   }
15338   return FALSE;
15339 }
15340
15341 int
15342 StringFeature(p, name, loc, cps)
15343      char **p;
15344      char *name;
15345      char loc[];
15346      ChessProgramState *cps;
15347 {
15348   char buf[MSG_SIZ];
15349   int len = strlen(name);
15350   if (strncmp((*p), name, len) == 0
15351       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15352     (*p) += len + 2;
15353     sscanf(*p, "%[^\"]", loc);
15354     while (**p && **p != '\"') (*p)++;
15355     if (**p == '\"') (*p)++;
15356     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15357     SendToProgram(buf, cps);
15358     return TRUE;
15359   }
15360   return FALSE;
15361 }
15362
15363 int
15364 ParseOption(Option *opt, ChessProgramState *cps)
15365 // [HGM] options: process the string that defines an engine option, and determine
15366 // name, type, default value, and allowed value range
15367 {
15368         char *p, *q, buf[MSG_SIZ];
15369         int n, min = (-1)<<31, max = 1<<31, def;
15370
15371         if(p = strstr(opt->name, " -spin ")) {
15372             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15373             if(max < min) max = min; // enforce consistency
15374             if(def < min) def = min;
15375             if(def > max) def = max;
15376             opt->value = def;
15377             opt->min = min;
15378             opt->max = max;
15379             opt->type = Spin;
15380         } else if((p = strstr(opt->name, " -slider "))) {
15381             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15382             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15383             if(max < min) max = min; // enforce consistency
15384             if(def < min) def = min;
15385             if(def > max) def = max;
15386             opt->value = def;
15387             opt->min = min;
15388             opt->max = max;
15389             opt->type = Spin; // Slider;
15390         } else if((p = strstr(opt->name, " -string "))) {
15391             opt->textValue = p+9;
15392             opt->type = TextBox;
15393         } else if((p = strstr(opt->name, " -file "))) {
15394             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15395             opt->textValue = p+7;
15396             opt->type = FileName; // FileName;
15397         } else if((p = strstr(opt->name, " -path "))) {
15398             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15399             opt->textValue = p+7;
15400             opt->type = PathName; // PathName;
15401         } else if(p = strstr(opt->name, " -check ")) {
15402             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15403             opt->value = (def != 0);
15404             opt->type = CheckBox;
15405         } else if(p = strstr(opt->name, " -combo ")) {
15406             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15407             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15408             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15409             opt->value = n = 0;
15410             while(q = StrStr(q, " /// ")) {
15411                 n++; *q = 0;    // count choices, and null-terminate each of them
15412                 q += 5;
15413                 if(*q == '*') { // remember default, which is marked with * prefix
15414                     q++;
15415                     opt->value = n;
15416                 }
15417                 cps->comboList[cps->comboCnt++] = q;
15418             }
15419             cps->comboList[cps->comboCnt++] = NULL;
15420             opt->max = n + 1;
15421             opt->type = ComboBox;
15422         } else if(p = strstr(opt->name, " -button")) {
15423             opt->type = Button;
15424         } else if(p = strstr(opt->name, " -save")) {
15425             opt->type = SaveButton;
15426         } else return FALSE;
15427         *p = 0; // terminate option name
15428         // now look if the command-line options define a setting for this engine option.
15429         if(cps->optionSettings && cps->optionSettings[0])
15430             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15431         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15432           snprintf(buf, MSG_SIZ, "option %s", p);
15433                 if(p = strstr(buf, ",")) *p = 0;
15434                 if(q = strchr(buf, '=')) switch(opt->type) {
15435                     case ComboBox:
15436                         for(n=0; n<opt->max; n++)
15437                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15438                         break;
15439                     case TextBox:
15440                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15441                         break;
15442                     case Spin:
15443                     case CheckBox:
15444                         opt->value = atoi(q+1);
15445                     default:
15446                         break;
15447                 }
15448                 strcat(buf, "\n");
15449                 SendToProgram(buf, cps);
15450         }
15451         return TRUE;
15452 }
15453
15454 void
15455 FeatureDone(cps, val)
15456      ChessProgramState* cps;
15457      int val;
15458 {
15459   DelayedEventCallback cb = GetDelayedEvent();
15460   if ((cb == InitBackEnd3 && cps == &first) ||
15461       (cb == SettingsMenuIfReady && cps == &second) ||
15462       (cb == LoadEngine) ||
15463       (cb == TwoMachinesEventIfReady)) {
15464     CancelDelayedEvent();
15465     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15466   }
15467   cps->initDone = val;
15468 }
15469
15470 /* Parse feature command from engine */
15471 void
15472 ParseFeatures(args, cps)
15473      char* args;
15474      ChessProgramState *cps;
15475 {
15476   char *p = args;
15477   char *q;
15478   int val;
15479   char buf[MSG_SIZ];
15480
15481   for (;;) {
15482     while (*p == ' ') p++;
15483     if (*p == NULLCHAR) return;
15484
15485     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15486     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15487     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15488     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15489     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15490     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15491     if (BoolFeature(&p, "reuse", &val, cps)) {
15492       /* Engine can disable reuse, but can't enable it if user said no */
15493       if (!val) cps->reuse = FALSE;
15494       continue;
15495     }
15496     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15497     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15498       if (gameMode == TwoMachinesPlay) {
15499         DisplayTwoMachinesTitle();
15500       } else {
15501         DisplayTitle("");
15502       }
15503       continue;
15504     }
15505     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15506     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15507     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15508     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15509     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15510     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15511     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15512     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15513     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15514     if (IntFeature(&p, "done", &val, cps)) {
15515       FeatureDone(cps, val);
15516       continue;
15517     }
15518     /* Added by Tord: */
15519     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15520     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15521     /* End of additions by Tord */
15522
15523     /* [HGM] added features: */
15524     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15525     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15526     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15527     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15528     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15529     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15530     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15531         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15532           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15533             SendToProgram(buf, cps);
15534             continue;
15535         }
15536         if(cps->nrOptions >= MAX_OPTIONS) {
15537             cps->nrOptions--;
15538             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15539             DisplayError(buf, 0);
15540         }
15541         continue;
15542     }
15543     /* End of additions by HGM */
15544
15545     /* unknown feature: complain and skip */
15546     q = p;
15547     while (*q && *q != '=') q++;
15548     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15549     SendToProgram(buf, cps);
15550     p = q;
15551     if (*p == '=') {
15552       p++;
15553       if (*p == '\"') {
15554         p++;
15555         while (*p && *p != '\"') p++;
15556         if (*p == '\"') p++;
15557       } else {
15558         while (*p && *p != ' ') p++;
15559       }
15560     }
15561   }
15562
15563 }
15564
15565 void
15566 PeriodicUpdatesEvent(newState)
15567      int newState;
15568 {
15569     if (newState == appData.periodicUpdates)
15570       return;
15571
15572     appData.periodicUpdates=newState;
15573
15574     /* Display type changes, so update it now */
15575 //    DisplayAnalysis();
15576
15577     /* Get the ball rolling again... */
15578     if (newState) {
15579         AnalysisPeriodicEvent(1);
15580         StartAnalysisClock();
15581     }
15582 }
15583
15584 void
15585 PonderNextMoveEvent(newState)
15586      int newState;
15587 {
15588     if (newState == appData.ponderNextMove) return;
15589     if (gameMode == EditPosition) EditPositionDone(TRUE);
15590     if (newState) {
15591         SendToProgram("hard\n", &first);
15592         if (gameMode == TwoMachinesPlay) {
15593             SendToProgram("hard\n", &second);
15594         }
15595     } else {
15596         SendToProgram("easy\n", &first);
15597         thinkOutput[0] = NULLCHAR;
15598         if (gameMode == TwoMachinesPlay) {
15599             SendToProgram("easy\n", &second);
15600         }
15601     }
15602     appData.ponderNextMove = newState;
15603 }
15604
15605 void
15606 NewSettingEvent(option, feature, command, value)
15607      char *command;
15608      int option, value, *feature;
15609 {
15610     char buf[MSG_SIZ];
15611
15612     if (gameMode == EditPosition) EditPositionDone(TRUE);
15613     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15614     if(feature == NULL || *feature) SendToProgram(buf, &first);
15615     if (gameMode == TwoMachinesPlay) {
15616         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15617     }
15618 }
15619
15620 void
15621 ShowThinkingEvent()
15622 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15623 {
15624     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15625     int newState = appData.showThinking
15626         // [HGM] thinking: other features now need thinking output as well
15627         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15628
15629     if (oldState == newState) return;
15630     oldState = newState;
15631     if (gameMode == EditPosition) EditPositionDone(TRUE);
15632     if (oldState) {
15633         SendToProgram("post\n", &first);
15634         if (gameMode == TwoMachinesPlay) {
15635             SendToProgram("post\n", &second);
15636         }
15637     } else {
15638         SendToProgram("nopost\n", &first);
15639         thinkOutput[0] = NULLCHAR;
15640         if (gameMode == TwoMachinesPlay) {
15641             SendToProgram("nopost\n", &second);
15642         }
15643     }
15644 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15645 }
15646
15647 void
15648 AskQuestionEvent(title, question, replyPrefix, which)
15649      char *title; char *question; char *replyPrefix; char *which;
15650 {
15651   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15652   if (pr == NoProc) return;
15653   AskQuestion(title, question, replyPrefix, pr);
15654 }
15655
15656 void
15657 TypeInEvent(char firstChar)
15658 {
15659     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15660         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15661         gameMode == AnalyzeMode || gameMode == EditGame || 
15662         gameMode == EditPosition || gameMode == IcsExamining ||
15663         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15664         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15665                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15666                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15667         gameMode == Training) PopUpMoveDialog(firstChar);
15668 }
15669
15670 void
15671 TypeInDoneEvent(char *move)
15672 {
15673         Board board;
15674         int n, fromX, fromY, toX, toY;
15675         char promoChar;
15676         ChessMove moveType;
15677
15678         // [HGM] FENedit
15679         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15680                 EditPositionPasteFEN(move);
15681                 return;
15682         }
15683         // [HGM] movenum: allow move number to be typed in any mode
15684         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15685           ToNrEvent(2*n-1);
15686           return;
15687         }
15688
15689       if (gameMode != EditGame && currentMove != forwardMostMove && 
15690         gameMode != Training) {
15691         DisplayMoveError(_("Displayed move is not current"));
15692       } else {
15693         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15694           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15695         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15696         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15697           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15698           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15699         } else {
15700           DisplayMoveError(_("Could not parse move"));
15701         }
15702       }
15703 }
15704
15705 void
15706 DisplayMove(moveNumber)
15707      int moveNumber;
15708 {
15709     char message[MSG_SIZ];
15710     char res[MSG_SIZ];
15711     char cpThinkOutput[MSG_SIZ];
15712
15713     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15714
15715     if (moveNumber == forwardMostMove - 1 ||
15716         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15717
15718         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15719
15720         if (strchr(cpThinkOutput, '\n')) {
15721             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15722         }
15723     } else {
15724         *cpThinkOutput = NULLCHAR;
15725     }
15726
15727     /* [AS] Hide thinking from human user */
15728     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15729         *cpThinkOutput = NULLCHAR;
15730         if( thinkOutput[0] != NULLCHAR ) {
15731             int i;
15732
15733             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15734                 cpThinkOutput[i] = '.';
15735             }
15736             cpThinkOutput[i] = NULLCHAR;
15737             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15738         }
15739     }
15740
15741     if (moveNumber == forwardMostMove - 1 &&
15742         gameInfo.resultDetails != NULL) {
15743         if (gameInfo.resultDetails[0] == NULLCHAR) {
15744           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15745         } else {
15746           snprintf(res, MSG_SIZ, " {%s} %s",
15747                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15748         }
15749     } else {
15750         res[0] = NULLCHAR;
15751     }
15752
15753     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15754         DisplayMessage(res, cpThinkOutput);
15755     } else {
15756       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15757                 WhiteOnMove(moveNumber) ? " " : ".. ",
15758                 parseList[moveNumber], res);
15759         DisplayMessage(message, cpThinkOutput);
15760     }
15761 }
15762
15763 void
15764 DisplayComment(moveNumber, text)
15765      int moveNumber;
15766      char *text;
15767 {
15768     char title[MSG_SIZ];
15769
15770     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15771       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15772     } else {
15773       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15774               WhiteOnMove(moveNumber) ? " " : ".. ",
15775               parseList[moveNumber]);
15776     }
15777     if (text != NULL && (appData.autoDisplayComment || commentUp))
15778         CommentPopUp(title, text);
15779 }
15780
15781 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15782  * might be busy thinking or pondering.  It can be omitted if your
15783  * gnuchess is configured to stop thinking immediately on any user
15784  * input.  However, that gnuchess feature depends on the FIONREAD
15785  * ioctl, which does not work properly on some flavors of Unix.
15786  */
15787 void
15788 Attention(cps)
15789      ChessProgramState *cps;
15790 {
15791 #if ATTENTION
15792     if (!cps->useSigint) return;
15793     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15794     switch (gameMode) {
15795       case MachinePlaysWhite:
15796       case MachinePlaysBlack:
15797       case TwoMachinesPlay:
15798       case IcsPlayingWhite:
15799       case IcsPlayingBlack:
15800       case AnalyzeMode:
15801       case AnalyzeFile:
15802         /* Skip if we know it isn't thinking */
15803         if (!cps->maybeThinking) return;
15804         if (appData.debugMode)
15805           fprintf(debugFP, "Interrupting %s\n", cps->which);
15806         InterruptChildProcess(cps->pr);
15807         cps->maybeThinking = FALSE;
15808         break;
15809       default:
15810         break;
15811     }
15812 #endif /*ATTENTION*/
15813 }
15814
15815 int
15816 CheckFlags()
15817 {
15818     if (whiteTimeRemaining <= 0) {
15819         if (!whiteFlag) {
15820             whiteFlag = TRUE;
15821             if (appData.icsActive) {
15822                 if (appData.autoCallFlag &&
15823                     gameMode == IcsPlayingBlack && !blackFlag) {
15824                   SendToICS(ics_prefix);
15825                   SendToICS("flag\n");
15826                 }
15827             } else {
15828                 if (blackFlag) {
15829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15830                 } else {
15831                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15832                     if (appData.autoCallFlag) {
15833                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15834                         return TRUE;
15835                     }
15836                 }
15837             }
15838         }
15839     }
15840     if (blackTimeRemaining <= 0) {
15841         if (!blackFlag) {
15842             blackFlag = TRUE;
15843             if (appData.icsActive) {
15844                 if (appData.autoCallFlag &&
15845                     gameMode == IcsPlayingWhite && !whiteFlag) {
15846                   SendToICS(ics_prefix);
15847                   SendToICS("flag\n");
15848                 }
15849             } else {
15850                 if (whiteFlag) {
15851                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15852                 } else {
15853                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15854                     if (appData.autoCallFlag) {
15855                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15856                         return TRUE;
15857                     }
15858                 }
15859             }
15860         }
15861     }
15862     return FALSE;
15863 }
15864
15865 void
15866 CheckTimeControl()
15867 {
15868     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15869         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15870
15871     /*
15872      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15873      */
15874     if ( !WhiteOnMove(forwardMostMove) ) {
15875         /* White made time control */
15876         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15877         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15878         /* [HGM] time odds: correct new time quota for time odds! */
15879                                             / WhitePlayer()->timeOdds;
15880         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15881     } else {
15882         lastBlack -= blackTimeRemaining;
15883         /* Black made time control */
15884         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15885                                             / WhitePlayer()->other->timeOdds;
15886         lastWhite = whiteTimeRemaining;
15887     }
15888 }
15889
15890 void
15891 DisplayBothClocks()
15892 {
15893     int wom = gameMode == EditPosition ?
15894       !blackPlaysFirst : WhiteOnMove(currentMove);
15895     DisplayWhiteClock(whiteTimeRemaining, wom);
15896     DisplayBlackClock(blackTimeRemaining, !wom);
15897 }
15898
15899
15900 /* Timekeeping seems to be a portability nightmare.  I think everyone
15901    has ftime(), but I'm really not sure, so I'm including some ifdefs
15902    to use other calls if you don't.  Clocks will be less accurate if
15903    you have neither ftime nor gettimeofday.
15904 */
15905
15906 /* VS 2008 requires the #include outside of the function */
15907 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15908 #include <sys/timeb.h>
15909 #endif
15910
15911 /* Get the current time as a TimeMark */
15912 void
15913 GetTimeMark(tm)
15914      TimeMark *tm;
15915 {
15916 #if HAVE_GETTIMEOFDAY
15917
15918     struct timeval timeVal;
15919     struct timezone timeZone;
15920
15921     gettimeofday(&timeVal, &timeZone);
15922     tm->sec = (long) timeVal.tv_sec;
15923     tm->ms = (int) (timeVal.tv_usec / 1000L);
15924
15925 #else /*!HAVE_GETTIMEOFDAY*/
15926 #if HAVE_FTIME
15927
15928 // include <sys/timeb.h> / moved to just above start of function
15929     struct timeb timeB;
15930
15931     ftime(&timeB);
15932     tm->sec = (long) timeB.time;
15933     tm->ms = (int) timeB.millitm;
15934
15935 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15936     tm->sec = (long) time(NULL);
15937     tm->ms = 0;
15938 #endif
15939 #endif
15940 }
15941
15942 /* Return the difference in milliseconds between two
15943    time marks.  We assume the difference will fit in a long!
15944 */
15945 long
15946 SubtractTimeMarks(tm2, tm1)
15947      TimeMark *tm2, *tm1;
15948 {
15949     return 1000L*(tm2->sec - tm1->sec) +
15950            (long) (tm2->ms - tm1->ms);
15951 }
15952
15953
15954 /*
15955  * Code to manage the game clocks.
15956  *
15957  * In tournament play, black starts the clock and then white makes a move.
15958  * We give the human user a slight advantage if he is playing white---the
15959  * clocks don't run until he makes his first move, so it takes zero time.
15960  * Also, we don't account for network lag, so we could get out of sync
15961  * with GNU Chess's clock -- but then, referees are always right.
15962  */
15963
15964 static TimeMark tickStartTM;
15965 static long intendedTickLength;
15966
15967 long
15968 NextTickLength(timeRemaining)
15969      long timeRemaining;
15970 {
15971     long nominalTickLength, nextTickLength;
15972
15973     if (timeRemaining > 0L && timeRemaining <= 10000L)
15974       nominalTickLength = 100L;
15975     else
15976       nominalTickLength = 1000L;
15977     nextTickLength = timeRemaining % nominalTickLength;
15978     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15979
15980     return nextTickLength;
15981 }
15982
15983 /* Adjust clock one minute up or down */
15984 void
15985 AdjustClock(Boolean which, int dir)
15986 {
15987     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15988     if(which) blackTimeRemaining += 60000*dir;
15989     else      whiteTimeRemaining += 60000*dir;
15990     DisplayBothClocks();
15991     adjustedClock = TRUE;
15992 }
15993
15994 /* Stop clocks and reset to a fresh time control */
15995 void
15996 ResetClocks()
15997 {
15998     (void) StopClockTimer();
15999     if (appData.icsActive) {
16000         whiteTimeRemaining = blackTimeRemaining = 0;
16001     } else if (searchTime) {
16002         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16003         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16004     } else { /* [HGM] correct new time quote for time odds */
16005         whiteTC = blackTC = fullTimeControlString;
16006         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16007         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16008     }
16009     if (whiteFlag || blackFlag) {
16010         DisplayTitle("");
16011         whiteFlag = blackFlag = FALSE;
16012     }
16013     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16014     DisplayBothClocks();
16015     adjustedClock = FALSE;
16016 }
16017
16018 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16019
16020 /* Decrement running clock by amount of time that has passed */
16021 void
16022 DecrementClocks()
16023 {
16024     long timeRemaining;
16025     long lastTickLength, fudge;
16026     TimeMark now;
16027
16028     if (!appData.clockMode) return;
16029     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16030
16031     GetTimeMark(&now);
16032
16033     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16034
16035     /* Fudge if we woke up a little too soon */
16036     fudge = intendedTickLength - lastTickLength;
16037     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16038
16039     if (WhiteOnMove(forwardMostMove)) {
16040         if(whiteNPS >= 0) lastTickLength = 0;
16041         timeRemaining = whiteTimeRemaining -= lastTickLength;
16042         if(timeRemaining < 0 && !appData.icsActive) {
16043             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16044             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16045                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16046                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16047             }
16048         }
16049         DisplayWhiteClock(whiteTimeRemaining - fudge,
16050                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16051     } else {
16052         if(blackNPS >= 0) lastTickLength = 0;
16053         timeRemaining = blackTimeRemaining -= lastTickLength;
16054         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16055             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16056             if(suddenDeath) {
16057                 blackStartMove = forwardMostMove;
16058                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16059             }
16060         }
16061         DisplayBlackClock(blackTimeRemaining - fudge,
16062                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16063     }
16064     if (CheckFlags()) return;
16065
16066     tickStartTM = now;
16067     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16068     StartClockTimer(intendedTickLength);
16069
16070     /* if the time remaining has fallen below the alarm threshold, sound the
16071      * alarm. if the alarm has sounded and (due to a takeback or time control
16072      * with increment) the time remaining has increased to a level above the
16073      * threshold, reset the alarm so it can sound again.
16074      */
16075
16076     if (appData.icsActive && appData.icsAlarm) {
16077
16078         /* make sure we are dealing with the user's clock */
16079         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16080                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16081            )) return;
16082
16083         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16084             alarmSounded = FALSE;
16085         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16086             PlayAlarmSound();
16087             alarmSounded = TRUE;
16088         }
16089     }
16090 }
16091
16092
16093 /* A player has just moved, so stop the previously running
16094    clock and (if in clock mode) start the other one.
16095    We redisplay both clocks in case we're in ICS mode, because
16096    ICS gives us an update to both clocks after every move.
16097    Note that this routine is called *after* forwardMostMove
16098    is updated, so the last fractional tick must be subtracted
16099    from the color that is *not* on move now.
16100 */
16101 void
16102 SwitchClocks(int newMoveNr)
16103 {
16104     long lastTickLength;
16105     TimeMark now;
16106     int flagged = FALSE;
16107
16108     GetTimeMark(&now);
16109
16110     if (StopClockTimer() && appData.clockMode) {
16111         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16112         if (!WhiteOnMove(forwardMostMove)) {
16113             if(blackNPS >= 0) lastTickLength = 0;
16114             blackTimeRemaining -= lastTickLength;
16115            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16116 //         if(pvInfoList[forwardMostMove].time == -1)
16117                  pvInfoList[forwardMostMove].time =               // use GUI time
16118                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16119         } else {
16120            if(whiteNPS >= 0) lastTickLength = 0;
16121            whiteTimeRemaining -= lastTickLength;
16122            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16123 //         if(pvInfoList[forwardMostMove].time == -1)
16124                  pvInfoList[forwardMostMove].time =
16125                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16126         }
16127         flagged = CheckFlags();
16128     }
16129     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16130     CheckTimeControl();
16131
16132     if (flagged || !appData.clockMode) return;
16133
16134     switch (gameMode) {
16135       case MachinePlaysBlack:
16136       case MachinePlaysWhite:
16137       case BeginningOfGame:
16138         if (pausing) return;
16139         break;
16140
16141       case EditGame:
16142       case PlayFromGameFile:
16143       case IcsExamining:
16144         return;
16145
16146       default:
16147         break;
16148     }
16149
16150     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16151         if(WhiteOnMove(forwardMostMove))
16152              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16153         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16154     }
16155
16156     tickStartTM = now;
16157     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16158       whiteTimeRemaining : blackTimeRemaining);
16159     StartClockTimer(intendedTickLength);
16160 }
16161
16162
16163 /* Stop both clocks */
16164 void
16165 StopClocks()
16166 {
16167     long lastTickLength;
16168     TimeMark now;
16169
16170     if (!StopClockTimer()) return;
16171     if (!appData.clockMode) return;
16172
16173     GetTimeMark(&now);
16174
16175     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16176     if (WhiteOnMove(forwardMostMove)) {
16177         if(whiteNPS >= 0) lastTickLength = 0;
16178         whiteTimeRemaining -= lastTickLength;
16179         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16180     } else {
16181         if(blackNPS >= 0) lastTickLength = 0;
16182         blackTimeRemaining -= lastTickLength;
16183         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16184     }
16185     CheckFlags();
16186 }
16187
16188 /* Start clock of player on move.  Time may have been reset, so
16189    if clock is already running, stop and restart it. */
16190 void
16191 StartClocks()
16192 {
16193     (void) StopClockTimer(); /* in case it was running already */
16194     DisplayBothClocks();
16195     if (CheckFlags()) return;
16196
16197     if (!appData.clockMode) return;
16198     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16199
16200     GetTimeMark(&tickStartTM);
16201     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16202       whiteTimeRemaining : blackTimeRemaining);
16203
16204    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16205     whiteNPS = blackNPS = -1;
16206     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16207        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16208         whiteNPS = first.nps;
16209     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16210        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16211         blackNPS = first.nps;
16212     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16213         whiteNPS = second.nps;
16214     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16215         blackNPS = second.nps;
16216     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16217
16218     StartClockTimer(intendedTickLength);
16219 }
16220
16221 char *
16222 TimeString(ms)
16223      long ms;
16224 {
16225     long second, minute, hour, day;
16226     char *sign = "";
16227     static char buf[32];
16228
16229     if (ms > 0 && ms <= 9900) {
16230       /* convert milliseconds to tenths, rounding up */
16231       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16232
16233       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16234       return buf;
16235     }
16236
16237     /* convert milliseconds to seconds, rounding up */
16238     /* use floating point to avoid strangeness of integer division
16239        with negative dividends on many machines */
16240     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16241
16242     if (second < 0) {
16243         sign = "-";
16244         second = -second;
16245     }
16246
16247     day = second / (60 * 60 * 24);
16248     second = second % (60 * 60 * 24);
16249     hour = second / (60 * 60);
16250     second = second % (60 * 60);
16251     minute = second / 60;
16252     second = second % 60;
16253
16254     if (day > 0)
16255       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16256               sign, day, hour, minute, second);
16257     else if (hour > 0)
16258       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16259     else
16260       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16261
16262     return buf;
16263 }
16264
16265
16266 /*
16267  * This is necessary because some C libraries aren't ANSI C compliant yet.
16268  */
16269 char *
16270 StrStr(string, match)
16271      char *string, *match;
16272 {
16273     int i, length;
16274
16275     length = strlen(match);
16276
16277     for (i = strlen(string) - length; i >= 0; i--, string++)
16278       if (!strncmp(match, string, length))
16279         return string;
16280
16281     return NULL;
16282 }
16283
16284 char *
16285 StrCaseStr(string, match)
16286      char *string, *match;
16287 {
16288     int i, j, length;
16289
16290     length = strlen(match);
16291
16292     for (i = strlen(string) - length; i >= 0; i--, string++) {
16293         for (j = 0; j < length; j++) {
16294             if (ToLower(match[j]) != ToLower(string[j]))
16295               break;
16296         }
16297         if (j == length) return string;
16298     }
16299
16300     return NULL;
16301 }
16302
16303 #ifndef _amigados
16304 int
16305 StrCaseCmp(s1, s2)
16306      char *s1, *s2;
16307 {
16308     char c1, c2;
16309
16310     for (;;) {
16311         c1 = ToLower(*s1++);
16312         c2 = ToLower(*s2++);
16313         if (c1 > c2) return 1;
16314         if (c1 < c2) return -1;
16315         if (c1 == NULLCHAR) return 0;
16316     }
16317 }
16318
16319
16320 int
16321 ToLower(c)
16322      int c;
16323 {
16324     return isupper(c) ? tolower(c) : c;
16325 }
16326
16327
16328 int
16329 ToUpper(c)
16330      int c;
16331 {
16332     return islower(c) ? toupper(c) : c;
16333 }
16334 #endif /* !_amigados    */
16335
16336 char *
16337 StrSave(s)
16338      char *s;
16339 {
16340   char *ret;
16341
16342   if ((ret = (char *) malloc(strlen(s) + 1)))
16343     {
16344       safeStrCpy(ret, s, strlen(s)+1);
16345     }
16346   return ret;
16347 }
16348
16349 char *
16350 StrSavePtr(s, savePtr)
16351      char *s, **savePtr;
16352 {
16353     if (*savePtr) {
16354         free(*savePtr);
16355     }
16356     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16357       safeStrCpy(*savePtr, s, strlen(s)+1);
16358     }
16359     return(*savePtr);
16360 }
16361
16362 char *
16363 PGNDate()
16364 {
16365     time_t clock;
16366     struct tm *tm;
16367     char buf[MSG_SIZ];
16368
16369     clock = time((time_t *)NULL);
16370     tm = localtime(&clock);
16371     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16372             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16373     return StrSave(buf);
16374 }
16375
16376
16377 char *
16378 PositionToFEN(move, overrideCastling)
16379      int move;
16380      char *overrideCastling;
16381 {
16382     int i, j, fromX, fromY, toX, toY;
16383     int whiteToPlay;
16384     char buf[MSG_SIZ];
16385     char *p, *q;
16386     int emptycount;
16387     ChessSquare piece;
16388
16389     whiteToPlay = (gameMode == EditPosition) ?
16390       !blackPlaysFirst : (move % 2 == 0);
16391     p = buf;
16392
16393     /* Piece placement data */
16394     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16395         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16396         emptycount = 0;
16397         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16398             if (boards[move][i][j] == EmptySquare) {
16399                 emptycount++;
16400             } else { ChessSquare piece = boards[move][i][j];
16401                 if (emptycount > 0) {
16402                     if(emptycount<10) /* [HGM] can be >= 10 */
16403                         *p++ = '0' + emptycount;
16404                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16405                     emptycount = 0;
16406                 }
16407                 if(PieceToChar(piece) == '+') {
16408                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16409                     *p++ = '+';
16410                     piece = (ChessSquare)(DEMOTED piece);
16411                 }
16412                 *p++ = PieceToChar(piece);
16413                 if(p[-1] == '~') {
16414                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16415                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16416                     *p++ = '~';
16417                 }
16418             }
16419         }
16420         if (emptycount > 0) {
16421             if(emptycount<10) /* [HGM] can be >= 10 */
16422                 *p++ = '0' + emptycount;
16423             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16424             emptycount = 0;
16425         }
16426         *p++ = '/';
16427     }
16428     *(p - 1) = ' ';
16429
16430     /* [HGM] print Crazyhouse or Shogi holdings */
16431     if( gameInfo.holdingsWidth ) {
16432         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16433         q = p;
16434         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16435             piece = boards[move][i][BOARD_WIDTH-1];
16436             if( piece != EmptySquare )
16437               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16438                   *p++ = PieceToChar(piece);
16439         }
16440         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16441             piece = boards[move][BOARD_HEIGHT-i-1][0];
16442             if( piece != EmptySquare )
16443               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16444                   *p++ = PieceToChar(piece);
16445         }
16446
16447         if( q == p ) *p++ = '-';
16448         *p++ = ']';
16449         *p++ = ' ';
16450     }
16451
16452     /* Active color */
16453     *p++ = whiteToPlay ? 'w' : 'b';
16454     *p++ = ' ';
16455
16456   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16457     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16458   } else {
16459   if(nrCastlingRights) {
16460      q = p;
16461      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16462        /* [HGM] write directly from rights */
16463            if(boards[move][CASTLING][2] != NoRights &&
16464               boards[move][CASTLING][0] != NoRights   )
16465                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16466            if(boards[move][CASTLING][2] != NoRights &&
16467               boards[move][CASTLING][1] != NoRights   )
16468                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16469            if(boards[move][CASTLING][5] != NoRights &&
16470               boards[move][CASTLING][3] != NoRights   )
16471                 *p++ = boards[move][CASTLING][3] + AAA;
16472            if(boards[move][CASTLING][5] != NoRights &&
16473               boards[move][CASTLING][4] != NoRights   )
16474                 *p++ = boards[move][CASTLING][4] + AAA;
16475      } else {
16476
16477         /* [HGM] write true castling rights */
16478         if( nrCastlingRights == 6 ) {
16479             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16480                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16481             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16482                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16483             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16484                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16485             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16486                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16487         }
16488      }
16489      if (q == p) *p++ = '-'; /* No castling rights */
16490      *p++ = ' ';
16491   }
16492
16493   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16494      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16495     /* En passant target square */
16496     if (move > backwardMostMove) {
16497         fromX = moveList[move - 1][0] - AAA;
16498         fromY = moveList[move - 1][1] - ONE;
16499         toX = moveList[move - 1][2] - AAA;
16500         toY = moveList[move - 1][3] - ONE;
16501         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16502             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16503             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16504             fromX == toX) {
16505             /* 2-square pawn move just happened */
16506             *p++ = toX + AAA;
16507             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16508         } else {
16509             *p++ = '-';
16510         }
16511     } else if(move == backwardMostMove) {
16512         // [HGM] perhaps we should always do it like this, and forget the above?
16513         if((signed char)boards[move][EP_STATUS] >= 0) {
16514             *p++ = boards[move][EP_STATUS] + AAA;
16515             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16516         } else {
16517             *p++ = '-';
16518         }
16519     } else {
16520         *p++ = '-';
16521     }
16522     *p++ = ' ';
16523   }
16524   }
16525
16526     /* [HGM] find reversible plies */
16527     {   int i = 0, j=move;
16528
16529         if (appData.debugMode) { int k;
16530             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16531             for(k=backwardMostMove; k<=forwardMostMove; k++)
16532                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16533
16534         }
16535
16536         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16537         if( j == backwardMostMove ) i += initialRulePlies;
16538         sprintf(p, "%d ", i);
16539         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16540     }
16541     /* Fullmove number */
16542     sprintf(p, "%d", (move / 2) + 1);
16543
16544     return StrSave(buf);
16545 }
16546
16547 Boolean
16548 ParseFEN(board, blackPlaysFirst, fen)
16549     Board board;
16550      int *blackPlaysFirst;
16551      char *fen;
16552 {
16553     int i, j;
16554     char *p, c;
16555     int emptycount;
16556     ChessSquare piece;
16557
16558     p = fen;
16559
16560     /* [HGM] by default clear Crazyhouse holdings, if present */
16561     if(gameInfo.holdingsWidth) {
16562        for(i=0; i<BOARD_HEIGHT; i++) {
16563            board[i][0]             = EmptySquare; /* black holdings */
16564            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16565            board[i][1]             = (ChessSquare) 0; /* black counts */
16566            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16567        }
16568     }
16569
16570     /* Piece placement data */
16571     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16572         j = 0;
16573         for (;;) {
16574             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16575                 if (*p == '/') p++;
16576                 emptycount = gameInfo.boardWidth - j;
16577                 while (emptycount--)
16578                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16579                 break;
16580 #if(BOARD_FILES >= 10)
16581             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16582                 p++; emptycount=10;
16583                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16584                 while (emptycount--)
16585                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16586 #endif
16587             } else if (isdigit(*p)) {
16588                 emptycount = *p++ - '0';
16589                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16590                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16591                 while (emptycount--)
16592                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16593             } else if (*p == '+' || isalpha(*p)) {
16594                 if (j >= gameInfo.boardWidth) return FALSE;
16595                 if(*p=='+') {
16596                     piece = CharToPiece(*++p);
16597                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16598                     piece = (ChessSquare) (PROMOTED piece ); p++;
16599                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16600                 } else piece = CharToPiece(*p++);
16601
16602                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16603                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16604                     piece = (ChessSquare) (PROMOTED piece);
16605                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16606                     p++;
16607                 }
16608                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16609             } else {
16610                 return FALSE;
16611             }
16612         }
16613     }
16614     while (*p == '/' || *p == ' ') p++;
16615
16616     /* [HGM] look for Crazyhouse holdings here */
16617     while(*p==' ') p++;
16618     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16619         if(*p == '[') p++;
16620         if(*p == '-' ) p++; /* empty holdings */ else {
16621             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16622             /* if we would allow FEN reading to set board size, we would   */
16623             /* have to add holdings and shift the board read so far here   */
16624             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16625                 p++;
16626                 if((int) piece >= (int) BlackPawn ) {
16627                     i = (int)piece - (int)BlackPawn;
16628                     i = PieceToNumber((ChessSquare)i);
16629                     if( i >= gameInfo.holdingsSize ) return FALSE;
16630                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16631                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16632                 } else {
16633                     i = (int)piece - (int)WhitePawn;
16634                     i = PieceToNumber((ChessSquare)i);
16635                     if( i >= gameInfo.holdingsSize ) return FALSE;
16636                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16637                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16638                 }
16639             }
16640         }
16641         if(*p == ']') p++;
16642     }
16643
16644     while(*p == ' ') p++;
16645
16646     /* Active color */
16647     c = *p++;
16648     if(appData.colorNickNames) {
16649       if( c == appData.colorNickNames[0] ) c = 'w'; else
16650       if( c == appData.colorNickNames[1] ) c = 'b';
16651     }
16652     switch (c) {
16653       case 'w':
16654         *blackPlaysFirst = FALSE;
16655         break;
16656       case 'b':
16657         *blackPlaysFirst = TRUE;
16658         break;
16659       default:
16660         return FALSE;
16661     }
16662
16663     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16664     /* return the extra info in global variiables             */
16665
16666     /* set defaults in case FEN is incomplete */
16667     board[EP_STATUS] = EP_UNKNOWN;
16668     for(i=0; i<nrCastlingRights; i++ ) {
16669         board[CASTLING][i] =
16670             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16671     }   /* assume possible unless obviously impossible */
16672     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16673     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16674     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16675                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16676     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16677     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16678     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16679                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16680     FENrulePlies = 0;
16681
16682     while(*p==' ') p++;
16683     if(nrCastlingRights) {
16684       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16685           /* castling indicator present, so default becomes no castlings */
16686           for(i=0; i<nrCastlingRights; i++ ) {
16687                  board[CASTLING][i] = NoRights;
16688           }
16689       }
16690       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16691              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16692              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16693              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16694         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16695
16696         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16697             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16698             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16699         }
16700         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16701             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16702         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16703                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16704         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16705                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16706         switch(c) {
16707           case'K':
16708               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16709               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16710               board[CASTLING][2] = whiteKingFile;
16711               break;
16712           case'Q':
16713               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16714               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16715               board[CASTLING][2] = whiteKingFile;
16716               break;
16717           case'k':
16718               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16719               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16720               board[CASTLING][5] = blackKingFile;
16721               break;
16722           case'q':
16723               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16724               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16725               board[CASTLING][5] = blackKingFile;
16726           case '-':
16727               break;
16728           default: /* FRC castlings */
16729               if(c >= 'a') { /* black rights */
16730                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16731                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16732                   if(i == BOARD_RGHT) break;
16733                   board[CASTLING][5] = i;
16734                   c -= AAA;
16735                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16736                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16737                   if(c > i)
16738                       board[CASTLING][3] = c;
16739                   else
16740                       board[CASTLING][4] = c;
16741               } else { /* white rights */
16742                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16743                     if(board[0][i] == WhiteKing) break;
16744                   if(i == BOARD_RGHT) break;
16745                   board[CASTLING][2] = i;
16746                   c -= AAA - 'a' + 'A';
16747                   if(board[0][c] >= WhiteKing) break;
16748                   if(c > i)
16749                       board[CASTLING][0] = c;
16750                   else
16751                       board[CASTLING][1] = c;
16752               }
16753         }
16754       }
16755       for(i=0; i<nrCastlingRights; i++)
16756         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16757     if (appData.debugMode) {
16758         fprintf(debugFP, "FEN castling rights:");
16759         for(i=0; i<nrCastlingRights; i++)
16760         fprintf(debugFP, " %d", board[CASTLING][i]);
16761         fprintf(debugFP, "\n");
16762     }
16763
16764       while(*p==' ') p++;
16765     }
16766
16767     /* read e.p. field in games that know e.p. capture */
16768     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16769        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16770       if(*p=='-') {
16771         p++; board[EP_STATUS] = EP_NONE;
16772       } else {
16773          char c = *p++ - AAA;
16774
16775          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16776          if(*p >= '0' && *p <='9') p++;
16777          board[EP_STATUS] = c;
16778       }
16779     }
16780
16781
16782     if(sscanf(p, "%d", &i) == 1) {
16783         FENrulePlies = i; /* 50-move ply counter */
16784         /* (The move number is still ignored)    */
16785     }
16786
16787     return TRUE;
16788 }
16789
16790 void
16791 EditPositionPasteFEN(char *fen)
16792 {
16793   if (fen != NULL) {
16794     Board initial_position;
16795
16796     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16797       DisplayError(_("Bad FEN position in clipboard"), 0);
16798       return ;
16799     } else {
16800       int savedBlackPlaysFirst = blackPlaysFirst;
16801       EditPositionEvent();
16802       blackPlaysFirst = savedBlackPlaysFirst;
16803       CopyBoard(boards[0], initial_position);
16804       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16805       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16806       DisplayBothClocks();
16807       DrawPosition(FALSE, boards[currentMove]);
16808     }
16809   }
16810 }
16811
16812 static char cseq[12] = "\\   ";
16813
16814 Boolean set_cont_sequence(char *new_seq)
16815 {
16816     int len;
16817     Boolean ret;
16818
16819     // handle bad attempts to set the sequence
16820         if (!new_seq)
16821                 return 0; // acceptable error - no debug
16822
16823     len = strlen(new_seq);
16824     ret = (len > 0) && (len < sizeof(cseq));
16825     if (ret)
16826       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16827     else if (appData.debugMode)
16828       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16829     return ret;
16830 }
16831
16832 /*
16833     reformat a source message so words don't cross the width boundary.  internal
16834     newlines are not removed.  returns the wrapped size (no null character unless
16835     included in source message).  If dest is NULL, only calculate the size required
16836     for the dest buffer.  lp argument indicats line position upon entry, and it's
16837     passed back upon exit.
16838 */
16839 int wrap(char *dest, char *src, int count, int width, int *lp)
16840 {
16841     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16842
16843     cseq_len = strlen(cseq);
16844     old_line = line = *lp;
16845     ansi = len = clen = 0;
16846
16847     for (i=0; i < count; i++)
16848     {
16849         if (src[i] == '\033')
16850             ansi = 1;
16851
16852         // if we hit the width, back up
16853         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16854         {
16855             // store i & len in case the word is too long
16856             old_i = i, old_len = len;
16857
16858             // find the end of the last word
16859             while (i && src[i] != ' ' && src[i] != '\n')
16860             {
16861                 i--;
16862                 len--;
16863             }
16864
16865             // word too long?  restore i & len before splitting it
16866             if ((old_i-i+clen) >= width)
16867             {
16868                 i = old_i;
16869                 len = old_len;
16870             }
16871
16872             // extra space?
16873             if (i && src[i-1] == ' ')
16874                 len--;
16875
16876             if (src[i] != ' ' && src[i] != '\n')
16877             {
16878                 i--;
16879                 if (len)
16880                     len--;
16881             }
16882
16883             // now append the newline and continuation sequence
16884             if (dest)
16885                 dest[len] = '\n';
16886             len++;
16887             if (dest)
16888                 strncpy(dest+len, cseq, cseq_len);
16889             len += cseq_len;
16890             line = cseq_len;
16891             clen = cseq_len;
16892             continue;
16893         }
16894
16895         if (dest)
16896             dest[len] = src[i];
16897         len++;
16898         if (!ansi)
16899             line++;
16900         if (src[i] == '\n')
16901             line = 0;
16902         if (src[i] == 'm')
16903             ansi = 0;
16904     }
16905     if (dest && appData.debugMode)
16906     {
16907         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16908             count, width, line, len, *lp);
16909         show_bytes(debugFP, src, count);
16910         fprintf(debugFP, "\ndest: ");
16911         show_bytes(debugFP, dest, len);
16912         fprintf(debugFP, "\n");
16913     }
16914     *lp = dest ? line : old_line;
16915
16916     return len;
16917 }
16918
16919 // [HGM] vari: routines for shelving variations
16920 Boolean modeRestore = FALSE;
16921
16922 void
16923 PushInner(int firstMove, int lastMove)
16924 {
16925         int i, j, nrMoves = lastMove - firstMove;
16926
16927         // push current tail of game on stack
16928         savedResult[storedGames] = gameInfo.result;
16929         savedDetails[storedGames] = gameInfo.resultDetails;
16930         gameInfo.resultDetails = NULL;
16931         savedFirst[storedGames] = firstMove;
16932         savedLast [storedGames] = lastMove;
16933         savedFramePtr[storedGames] = framePtr;
16934         framePtr -= nrMoves; // reserve space for the boards
16935         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16936             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16937             for(j=0; j<MOVE_LEN; j++)
16938                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16939             for(j=0; j<2*MOVE_LEN; j++)
16940                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16941             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16942             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16943             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16944             pvInfoList[firstMove+i-1].depth = 0;
16945             commentList[framePtr+i] = commentList[firstMove+i];
16946             commentList[firstMove+i] = NULL;
16947         }
16948
16949         storedGames++;
16950         forwardMostMove = firstMove; // truncate game so we can start variation
16951 }
16952
16953 void
16954 PushTail(int firstMove, int lastMove)
16955 {
16956         if(appData.icsActive) { // only in local mode
16957                 forwardMostMove = currentMove; // mimic old ICS behavior
16958                 return;
16959         }
16960         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16961
16962         PushInner(firstMove, lastMove);
16963         if(storedGames == 1) GreyRevert(FALSE);
16964         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16965 }
16966
16967 void
16968 PopInner(Boolean annotate)
16969 {
16970         int i, j, nrMoves;
16971         char buf[8000], moveBuf[20];
16972
16973         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16974         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16975         nrMoves = savedLast[storedGames] - currentMove;
16976         if(annotate) {
16977                 int cnt = 10;
16978                 if(!WhiteOnMove(currentMove))
16979                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16980                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16981                 for(i=currentMove; i<forwardMostMove; i++) {
16982                         if(WhiteOnMove(i))
16983                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16984                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16985                         strcat(buf, moveBuf);
16986                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16987                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16988                 }
16989                 strcat(buf, ")");
16990         }
16991         for(i=1; i<=nrMoves; i++) { // copy last variation back
16992             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16993             for(j=0; j<MOVE_LEN; j++)
16994                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16995             for(j=0; j<2*MOVE_LEN; j++)
16996                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16997             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16998             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16999             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17000             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17001             commentList[currentMove+i] = commentList[framePtr+i];
17002             commentList[framePtr+i] = NULL;
17003         }
17004         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17005         framePtr = savedFramePtr[storedGames];
17006         gameInfo.result = savedResult[storedGames];
17007         if(gameInfo.resultDetails != NULL) {
17008             free(gameInfo.resultDetails);
17009       }
17010         gameInfo.resultDetails = savedDetails[storedGames];
17011         forwardMostMove = currentMove + nrMoves;
17012 }
17013
17014 Boolean
17015 PopTail(Boolean annotate)
17016 {
17017         if(appData.icsActive) return FALSE; // only in local mode
17018         if(!storedGames) return FALSE; // sanity
17019         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17020
17021         PopInner(annotate);
17022         if(currentMove < forwardMostMove) ForwardEvent(); else
17023         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17024
17025         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17026         return TRUE;
17027 }
17028
17029 void
17030 CleanupTail()
17031 {       // remove all shelved variations
17032         int i;
17033         for(i=0; i<storedGames; i++) {
17034             if(savedDetails[i])
17035                 free(savedDetails[i]);
17036             savedDetails[i] = NULL;
17037         }
17038         for(i=framePtr; i<MAX_MOVES; i++) {
17039                 if(commentList[i]) free(commentList[i]);
17040                 commentList[i] = NULL;
17041         }
17042         framePtr = MAX_MOVES-1;
17043         storedGames = 0;
17044 }
17045
17046 void
17047 LoadVariation(int index, char *text)
17048 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17049         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17050         int level = 0, move;
17051
17052         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17053         // first find outermost bracketing variation
17054         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17055             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17056                 if(*p == '{') wait = '}'; else
17057                 if(*p == '[') wait = ']'; else
17058                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17059                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17060             }
17061             if(*p == wait) wait = NULLCHAR; // closing ]} found
17062             p++;
17063         }
17064         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17065         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17066         end[1] = NULLCHAR; // clip off comment beyond variation
17067         ToNrEvent(currentMove-1);
17068         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17069         // kludge: use ParsePV() to append variation to game
17070         move = currentMove;
17071         ParsePV(start, TRUE, TRUE);
17072         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17073         ClearPremoveHighlights();
17074         CommentPopDown();
17075         ToNrEvent(currentMove+1);
17076 }
17077