Fix ICS castling rights
[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             // [HGM] chat: intercept tells by users for which we have an open chat window
3130             channel = -1;
3131             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3132                                            looking_at(buf, &i, "* whispers:") ||
3133                                            looking_at(buf, &i, "* kibitzes:") ||
3134                                            looking_at(buf, &i, "* shouts:") ||
3135                                            looking_at(buf, &i, "* c-shouts:") ||
3136                                            looking_at(buf, &i, "--> * ") ||
3137                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3140                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3141                 int p;
3142                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3143                 chattingPartner = -1;
3144
3145                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3146                 for(p=0; p<MAX_CHAT; p++) {
3147                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3148                     talker[0] = '['; strcat(talker, "] ");
3149                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3150                     chattingPartner = p; break;
3151                     }
3152                 } else
3153                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(!strcmp("kibitzes", chatPartner[p])) {
3156                         talker[0] = '['; strcat(talker, "] ");
3157                         chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("whispers", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3168                   if(buf[i-8] == '-' && buf[i-3] == 't')
3169                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3170                     if(!strcmp("c-shouts", chatPartner[p])) {
3171                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3172                         chattingPartner = p; break;
3173                     }
3174                   }
3175                   if(chattingPartner < 0)
3176                   for(p=0; p<MAX_CHAT; p++) {
3177                     if(!strcmp("shouts", chatPartner[p])) {
3178                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3179                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3180                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                 }
3185                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3186                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3187                     talker[0] = 0; Colorize(ColorTell, FALSE);
3188                     chattingPartner = p; break;
3189                 }
3190                 if(chattingPartner<0) i = oldi; else {
3191                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3192                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3193                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194                     started = STARTED_COMMENT;
3195                     parse_pos = 0; parse[0] = NULLCHAR;
3196                     savingComment = 3 + chattingPartner; // counts as TRUE
3197                     suppressKibitz = TRUE;
3198                     continue;
3199                 }
3200             } // [HGM] chat: end of patch
3201
3202           backup = i;
3203             if (appData.zippyTalk || appData.zippyPlay) {
3204                 /* [DM] Backup address for color zippy lines */
3205 #if ZIPPY
3206                if (loggedOn == TRUE)
3207                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3208                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3209 #endif
3210             } // [DM] 'else { ' deleted
3211                 if (
3212                     /* Regular tells and says */
3213                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3214                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3215                     looking_at(buf, &i, "* says: ") ||
3216                     /* Don't color "message" or "messages" output */
3217                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3218                     looking_at(buf, &i, "*. * at *:*: ") ||
3219                     looking_at(buf, &i, "--* (*:*): ") ||
3220                     /* Message notifications (same color as tells) */
3221                     looking_at(buf, &i, "* has left a message ") ||
3222                     looking_at(buf, &i, "* just sent you a message:\n") ||
3223                     /* Whispers and kibitzes */
3224                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3225                     looking_at(buf, &i, "* kibitzes: ") ||
3226                     /* Channel tells */
3227                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3228
3229                   if (tkind == 1 && strchr(star_match[0], ':')) {
3230                       /* Avoid "tells you:" spoofs in channels */
3231                      tkind = 3;
3232                   }
3233                   if (star_match[0][0] == NULLCHAR ||
3234                       strchr(star_match[0], ' ') ||
3235                       (tkind == 3 && strchr(star_match[1], ' '))) {
3236                     /* Reject bogus matches */
3237                     i = oldi;
3238                   } else {
3239                     if (appData.colorize) {
3240                       if (oldi > next_out) {
3241                         SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = oldi;
3243                       }
3244                       switch (tkind) {
3245                       case 1:
3246                         Colorize(ColorTell, FALSE);
3247                         curColor = ColorTell;
3248                         break;
3249                       case 2:
3250                         Colorize(ColorKibitz, FALSE);
3251                         curColor = ColorKibitz;
3252                         break;
3253                       case 3:
3254                         p = strrchr(star_match[1], '(');
3255                         if (p == NULL) {
3256                           p = star_match[1];
3257                         } else {
3258                           p++;
3259                         }
3260                         if (atoi(p) == 1) {
3261                           Colorize(ColorChannel1, FALSE);
3262                           curColor = ColorChannel1;
3263                         } else {
3264                           Colorize(ColorChannel, FALSE);
3265                           curColor = ColorChannel;
3266                         }
3267                         break;
3268                       case 5:
3269                         curColor = ColorNormal;
3270                         break;
3271                       }
3272                     }
3273                     if (started == STARTED_NONE && appData.autoComment &&
3274                         (gameMode == IcsObserving ||
3275                          gameMode == IcsPlayingWhite ||
3276                          gameMode == IcsPlayingBlack)) {
3277                       parse_pos = i - oldi;
3278                       memcpy(parse, &buf[oldi], parse_pos);
3279                       parse[parse_pos] = NULLCHAR;
3280                       started = STARTED_COMMENT;
3281                       savingComment = TRUE;
3282                     } else {
3283                       started = STARTED_CHATTER;
3284                       savingComment = FALSE;
3285                     }
3286                     loggedOn = TRUE;
3287                     continue;
3288                   }
3289                 }
3290
3291                 if (looking_at(buf, &i, "* s-shouts: ") ||
3292                     looking_at(buf, &i, "* c-shouts: ")) {
3293                     if (appData.colorize) {
3294                         if (oldi > next_out) {
3295                             SendToPlayer(&buf[next_out], oldi - next_out);
3296                             next_out = oldi;
3297                         }
3298                         Colorize(ColorSShout, FALSE);
3299                         curColor = ColorSShout;
3300                     }
3301                     loggedOn = TRUE;
3302                     started = STARTED_CHATTER;
3303                     continue;
3304                 }
3305
3306                 if (looking_at(buf, &i, "--->")) {
3307                     loggedOn = TRUE;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* shouts: ") ||
3312                     looking_at(buf, &i, "--> ")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorShout, FALSE);
3319                         curColor = ColorShout;
3320                     }
3321                     loggedOn = TRUE;
3322                     started = STARTED_CHATTER;
3323                     continue;
3324                 }
3325
3326                 if (looking_at( buf, &i, "Challenge:")) {
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorChallenge, FALSE);
3333                         curColor = ColorChallenge;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                 }
3338
3339                 if (looking_at(buf, &i, "* offers you") ||
3340                     looking_at(buf, &i, "* offers to be") ||
3341                     looking_at(buf, &i, "* would like to") ||
3342                     looking_at(buf, &i, "* requests to") ||
3343                     looking_at(buf, &i, "Your opponent offers") ||
3344                     looking_at(buf, &i, "Your opponent requests")) {
3345
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorRequest, FALSE);
3352                         curColor = ColorRequest;
3353                     }
3354                     continue;
3355                 }
3356
3357                 if (looking_at(buf, &i, "* (*) seeking")) {
3358                     if (appData.colorize) {
3359                         if (oldi > next_out) {
3360                             SendToPlayer(&buf[next_out], oldi - next_out);
3361                             next_out = oldi;
3362                         }
3363                         Colorize(ColorSeek, FALSE);
3364                         curColor = ColorSeek;
3365                     }
3366                     continue;
3367             }
3368
3369           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3370
3371             if (looking_at(buf, &i, "\\   ")) {
3372                 if (prevColor != ColorNormal) {
3373                     if (oldi > next_out) {
3374                         SendToPlayer(&buf[next_out], oldi - next_out);
3375                         next_out = oldi;
3376                     }
3377                     Colorize(prevColor, TRUE);
3378                     curColor = prevColor;
3379                 }
3380                 if (savingComment) {
3381                     parse_pos = i - oldi;
3382                     memcpy(parse, &buf[oldi], parse_pos);
3383                     parse[parse_pos] = NULLCHAR;
3384                     started = STARTED_COMMENT;
3385                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3386                         chattingPartner = savingComment - 3; // kludge to remember the box
3387                 } else {
3388                     started = STARTED_CHATTER;
3389                 }
3390                 continue;
3391             }
3392
3393             if (looking_at(buf, &i, "Black Strength :") ||
3394                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3395                 looking_at(buf, &i, "<10>") ||
3396                 looking_at(buf, &i, "#@#")) {
3397                 /* Wrong board style */
3398                 loggedOn = TRUE;
3399                 SendToICS(ics_prefix);
3400                 SendToICS("set style 12\n");
3401                 SendToICS(ics_prefix);
3402                 SendToICS("refresh\n");
3403                 continue;
3404             }
3405
3406             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3407                 ICSInitScript();
3408                 have_sent_ICS_logon = 1;
3409                 continue;
3410             }
3411
3412             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3413                 (looking_at(buf, &i, "\n<12> ") ||
3414                  looking_at(buf, &i, "<12> "))) {
3415                 loggedOn = TRUE;
3416                 if (oldi > next_out) {
3417                     SendToPlayer(&buf[next_out], oldi - next_out);
3418                 }
3419                 next_out = i;
3420                 started = STARTED_BOARD;
3421                 parse_pos = 0;
3422                 continue;
3423             }
3424
3425             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3426                 looking_at(buf, &i, "<b1> ")) {
3427                 if (oldi > next_out) {
3428                     SendToPlayer(&buf[next_out], oldi - next_out);
3429                 }
3430                 next_out = i;
3431                 started = STARTED_HOLDINGS;
3432                 parse_pos = 0;
3433                 continue;
3434             }
3435
3436             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3437                 loggedOn = TRUE;
3438                 /* Header for a move list -- first line */
3439
3440                 switch (ics_getting_history) {
3441                   case H_FALSE:
3442                     switch (gameMode) {
3443                       case IcsIdle:
3444                       case BeginningOfGame:
3445                         /* User typed "moves" or "oldmoves" while we
3446                            were idle.  Pretend we asked for these
3447                            moves and soak them up so user can step
3448                            through them and/or save them.
3449                            */
3450                         Reset(FALSE, TRUE);
3451                         gameMode = IcsObserving;
3452                         ModeHighlight();
3453                         ics_gamenum = -1;
3454                         ics_getting_history = H_GOT_UNREQ_HEADER;
3455                         break;
3456                       case EditGame: /*?*/
3457                       case EditPosition: /*?*/
3458                         /* Should above feature work in these modes too? */
3459                         /* For now it doesn't */
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                       default:
3463                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3464                         break;
3465                     }
3466                     break;
3467                   case H_REQUESTED:
3468                     /* Is this the right one? */
3469                     if (gameInfo.white && gameInfo.black &&
3470                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3471                         strcmp(gameInfo.black, star_match[2]) == 0) {
3472                         /* All is well */
3473                         ics_getting_history = H_GOT_REQ_HEADER;
3474                     }
3475                     break;
3476                   case H_GOT_REQ_HEADER:
3477                   case H_GOT_UNREQ_HEADER:
3478                   case H_GOT_UNWANTED_HEADER:
3479                   case H_GETTING_MOVES:
3480                     /* Should not happen */
3481                     DisplayError(_("Error gathering move list: two headers"), 0);
3482                     ics_getting_history = H_FALSE;
3483                     break;
3484                 }
3485
3486                 /* Save player ratings into gameInfo if needed */
3487                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3488                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3489                     (gameInfo.whiteRating == -1 ||
3490                      gameInfo.blackRating == -1)) {
3491
3492                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3493                     gameInfo.blackRating = string_to_rating(star_match[3]);
3494                     if (appData.debugMode)
3495                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3496                               gameInfo.whiteRating, gameInfo.blackRating);
3497                 }
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i,
3502               "* * match, initial time: * minute*, increment: * second")) {
3503                 /* Header for a move list -- second line */
3504                 /* Initial board will follow if this is a wild game */
3505                 if (gameInfo.event != NULL) free(gameInfo.event);
3506                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3507                 gameInfo.event = StrSave(str);
3508                 /* [HGM] we switched variant. Translate boards if needed. */
3509                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3510                 continue;
3511             }
3512
3513             if (looking_at(buf, &i, "Move  ")) {
3514                 /* Beginning of a move list */
3515                 switch (ics_getting_history) {
3516                   case H_FALSE:
3517                     /* Normally should not happen */
3518                     /* Maybe user hit reset while we were parsing */
3519                     break;
3520                   case H_REQUESTED:
3521                     /* Happens if we are ignoring a move list that is not
3522                      * the one we just requested.  Common if the user
3523                      * tries to observe two games without turning off
3524                      * getMoveList */
3525                     break;
3526                   case H_GETTING_MOVES:
3527                     /* Should not happen */
3528                     DisplayError(_("Error gathering move list: nested"), 0);
3529                     ics_getting_history = H_FALSE;
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                     ics_getting_history = H_GETTING_MOVES;
3533                     started = STARTED_MOVES;
3534                     parse_pos = 0;
3535                     if (oldi > next_out) {
3536                         SendToPlayer(&buf[next_out], oldi - next_out);
3537                     }
3538                     break;
3539                   case H_GOT_UNREQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES_NOHIDE;
3542                     parse_pos = 0;
3543                     break;
3544                   case H_GOT_UNWANTED_HEADER:
3545                     ics_getting_history = H_FALSE;
3546                     break;
3547                 }
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "% ") ||
3552                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3553                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3554                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3555                     soughtPending = FALSE;
3556                     seekGraphUp = TRUE;
3557                     DrawSeekGraph();
3558                 }
3559                 if(suppressKibitz) next_out = i;
3560                 savingComment = FALSE;
3561                 suppressKibitz = 0;
3562                 switch (started) {
3563                   case STARTED_MOVES:
3564                   case STARTED_MOVES_NOHIDE:
3565                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3566                     parse[parse_pos + i - oldi] = NULLCHAR;
3567                     ParseGameHistory(parse);
3568 #if ZIPPY
3569                     if (appData.zippyPlay && first.initDone) {
3570                         FeedMovesToProgram(&first, forwardMostMove);
3571                         if (gameMode == IcsPlayingWhite) {
3572                             if (WhiteOnMove(forwardMostMove)) {
3573                                 if (first.sendTime) {
3574                                   if (first.useColors) {
3575                                     SendToProgram("black\n", &first);
3576                                   }
3577                                   SendTimeRemaining(&first, TRUE);
3578                                 }
3579                                 if (first.useColors) {
3580                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3581                                 }
3582                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3583                                 first.maybeThinking = TRUE;
3584                             } else {
3585                                 if (first.usePlayother) {
3586                                   if (first.sendTime) {
3587                                     SendTimeRemaining(&first, TRUE);
3588                                   }
3589                                   SendToProgram("playother\n", &first);
3590                                   firstMove = FALSE;
3591                                 } else {
3592                                   firstMove = TRUE;
3593                                 }
3594                             }
3595                         } else if (gameMode == IcsPlayingBlack) {
3596                             if (!WhiteOnMove(forwardMostMove)) {
3597                                 if (first.sendTime) {
3598                                   if (first.useColors) {
3599                                     SendToProgram("white\n", &first);
3600                                   }
3601                                   SendTimeRemaining(&first, FALSE);
3602                                 }
3603                                 if (first.useColors) {
3604                                   SendToProgram("black\n", &first);
3605                                 }
3606                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3607                                 first.maybeThinking = TRUE;
3608                             } else {
3609                                 if (first.usePlayother) {
3610                                   if (first.sendTime) {
3611                                     SendTimeRemaining(&first, FALSE);
3612                                   }
3613                                   SendToProgram("playother\n", &first);
3614                                   firstMove = FALSE;
3615                                 } else {
3616                                   firstMove = TRUE;
3617                                 }
3618                             }
3619                         }
3620                     }
3621 #endif
3622                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3623                         /* Moves came from oldmoves or moves command
3624                            while we weren't doing anything else.
3625                            */
3626                         currentMove = forwardMostMove;
3627                         ClearHighlights();/*!!could figure this out*/
3628                         flipView = appData.flipView;
3629                         DrawPosition(TRUE, boards[currentMove]);
3630                         DisplayBothClocks();
3631                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3632                                 gameInfo.white, gameInfo.black);
3633                         DisplayTitle(str);
3634                         gameMode = IcsIdle;
3635                     } else {
3636                         /* Moves were history of an active game */
3637                         if (gameInfo.resultDetails != NULL) {
3638                             free(gameInfo.resultDetails);
3639                             gameInfo.resultDetails = NULL;
3640                         }
3641                     }
3642                     HistorySet(parseList, backwardMostMove,
3643                                forwardMostMove, currentMove-1);
3644                     DisplayMove(currentMove - 1);
3645                     if (started == STARTED_MOVES) next_out = i;
3646                     started = STARTED_NONE;
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649
3650                   case STARTED_OBSERVE:
3651                     started = STARTED_NONE;
3652                     SendToICS(ics_prefix);
3653                     SendToICS("refresh\n");
3654                     break;
3655
3656                   default:
3657                     break;
3658                 }
3659                 if(bookHit) { // [HGM] book: simulate book reply
3660                     static char bookMove[MSG_SIZ]; // a bit generous?
3661
3662                     programStats.nodes = programStats.depth = programStats.time =
3663                     programStats.score = programStats.got_only_move = 0;
3664                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3665
3666                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3667                     strcat(bookMove, bookHit);
3668                     HandleMachineMove(bookMove, &first);
3669                 }
3670                 continue;
3671             }
3672
3673             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3674                  started == STARTED_HOLDINGS ||
3675                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3676                 /* Accumulate characters in move list or board */
3677                 parse[parse_pos++] = buf[i];
3678             }
3679
3680             /* Start of game messages.  Mostly we detect start of game
3681                when the first board image arrives.  On some versions
3682                of the ICS, though, we need to do a "refresh" after starting
3683                to observe in order to get the current board right away. */
3684             if (looking_at(buf, &i, "Adding game * to observation list")) {
3685                 started = STARTED_OBSERVE;
3686                 continue;
3687             }
3688
3689             /* Handle auto-observe */
3690             if (appData.autoObserve &&
3691                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3692                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3693                 char *player;
3694                 /* Choose the player that was highlighted, if any. */
3695                 if (star_match[0][0] == '\033' ||
3696                     star_match[1][0] != '\033') {
3697                     player = star_match[0];
3698                 } else {
3699                     player = star_match[2];
3700                 }
3701                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3702                         ics_prefix, StripHighlightAndTitle(player));
3703                 SendToICS(str);
3704
3705                 /* Save ratings from notify string */
3706                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3707                 player1Rating = string_to_rating(star_match[1]);
3708                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3709                 player2Rating = string_to_rating(star_match[3]);
3710
3711                 if (appData.debugMode)
3712                   fprintf(debugFP,
3713                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3714                           player1Name, player1Rating,
3715                           player2Name, player2Rating);
3716
3717                 continue;
3718             }
3719
3720             /* Deal with automatic examine mode after a game,
3721                and with IcsObserving -> IcsExamining transition */
3722             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3723                 looking_at(buf, &i, "has made you an examiner of game *")) {
3724
3725                 int gamenum = atoi(star_match[0]);
3726                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3727                     gamenum == ics_gamenum) {
3728                     /* We were already playing or observing this game;
3729                        no need to refetch history */
3730                     gameMode = IcsExamining;
3731                     if (pausing) {
3732                         pauseExamForwardMostMove = forwardMostMove;
3733                     } else if (currentMove < forwardMostMove) {
3734                         ForwardInner(forwardMostMove);
3735                     }
3736                 } else {
3737                     /* I don't think this case really can happen */
3738                     SendToICS(ics_prefix);
3739                     SendToICS("refresh\n");
3740                 }
3741                 continue;
3742             }
3743
3744             /* Error messages */
3745 //          if (ics_user_moved) {
3746             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3747                 if (looking_at(buf, &i, "Illegal move") ||
3748                     looking_at(buf, &i, "Not a legal move") ||
3749                     looking_at(buf, &i, "Your king is in check") ||
3750                     looking_at(buf, &i, "It isn't your turn") ||
3751                     looking_at(buf, &i, "It is not your move")) {
3752                     /* Illegal move */
3753                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3754                         currentMove = forwardMostMove-1;
3755                         DisplayMove(currentMove - 1); /* before DMError */
3756                         DrawPosition(FALSE, boards[currentMove]);
3757                         SwitchClocks(forwardMostMove-1); // [HGM] race
3758                         DisplayBothClocks();
3759                     }
3760                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3761                     ics_user_moved = 0;
3762                     continue;
3763                 }
3764             }
3765
3766             if (looking_at(buf, &i, "still have time") ||
3767                 looking_at(buf, &i, "not out of time") ||
3768                 looking_at(buf, &i, "either player is out of time") ||
3769                 looking_at(buf, &i, "has timeseal; checking")) {
3770                 /* We must have called his flag a little too soon */
3771                 whiteFlag = blackFlag = FALSE;
3772                 continue;
3773             }
3774
3775             if (looking_at(buf, &i, "added * seconds to") ||
3776                 looking_at(buf, &i, "seconds were added to")) {
3777                 /* Update the clocks */
3778                 SendToICS(ics_prefix);
3779                 SendToICS("refresh\n");
3780                 continue;
3781             }
3782
3783             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3784                 ics_clock_paused = TRUE;
3785                 StopClocks();
3786                 continue;
3787             }
3788
3789             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3790                 ics_clock_paused = FALSE;
3791                 StartClocks();
3792                 continue;
3793             }
3794
3795             /* Grab player ratings from the Creating: message.
3796                Note we have to check for the special case when
3797                the ICS inserts things like [white] or [black]. */
3798             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3799                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3800                 /* star_matches:
3801                    0    player 1 name (not necessarily white)
3802                    1    player 1 rating
3803                    2    empty, white, or black (IGNORED)
3804                    3    player 2 name (not necessarily black)
3805                    4    player 2 rating
3806
3807                    The names/ratings are sorted out when the game
3808                    actually starts (below).
3809                 */
3810                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3811                 player1Rating = string_to_rating(star_match[1]);
3812                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3813                 player2Rating = string_to_rating(star_match[4]);
3814
3815                 if (appData.debugMode)
3816                   fprintf(debugFP,
3817                           "Ratings from 'Creating:' %s %d, %s %d\n",
3818                           player1Name, player1Rating,
3819                           player2Name, player2Rating);
3820
3821                 continue;
3822             }
3823
3824             /* Improved generic start/end-of-game messages */
3825             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3826                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3827                 /* If tkind == 0: */
3828                 /* star_match[0] is the game number */
3829                 /*           [1] is the white player's name */
3830                 /*           [2] is the black player's name */
3831                 /* For end-of-game: */
3832                 /*           [3] is the reason for the game end */
3833                 /*           [4] is a PGN end game-token, preceded by " " */
3834                 /* For start-of-game: */
3835                 /*           [3] begins with "Creating" or "Continuing" */
3836                 /*           [4] is " *" or empty (don't care). */
3837                 int gamenum = atoi(star_match[0]);
3838                 char *whitename, *blackname, *why, *endtoken;
3839                 ChessMove endtype = EndOfFile;
3840
3841                 if (tkind == 0) {
3842                   whitename = star_match[1];
3843                   blackname = star_match[2];
3844                   why = star_match[3];
3845                   endtoken = star_match[4];
3846                 } else {
3847                   whitename = star_match[1];
3848                   blackname = star_match[3];
3849                   why = star_match[5];
3850                   endtoken = star_match[6];
3851                 }
3852
3853                 /* Game start messages */
3854                 if (strncmp(why, "Creating ", 9) == 0 ||
3855                     strncmp(why, "Continuing ", 11) == 0) {
3856                     gs_gamenum = gamenum;
3857                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3858                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3859                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3860 #if ZIPPY
3861                     if (appData.zippyPlay) {
3862                         ZippyGameStart(whitename, blackname);
3863                     }
3864 #endif /*ZIPPY*/
3865                     partnerBoardValid = FALSE; // [HGM] bughouse
3866                     continue;
3867                 }
3868
3869                 /* Game end messages */
3870                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3871                     ics_gamenum != gamenum) {
3872                     continue;
3873                 }
3874                 while (endtoken[0] == ' ') endtoken++;
3875                 switch (endtoken[0]) {
3876                   case '*':
3877                   default:
3878                     endtype = GameUnfinished;
3879                     break;
3880                   case '0':
3881                     endtype = BlackWins;
3882                     break;
3883                   case '1':
3884                     if (endtoken[1] == '/')
3885                       endtype = GameIsDrawn;
3886                     else
3887                       endtype = WhiteWins;
3888                     break;
3889                 }
3890                 GameEnds(endtype, why, GE_ICS);
3891 #if ZIPPY
3892                 if (appData.zippyPlay && first.initDone) {
3893                     ZippyGameEnd(endtype, why);
3894                     if (first.pr == NoProc) {
3895                       /* Start the next process early so that we'll
3896                          be ready for the next challenge */
3897                       StartChessProgram(&first);
3898                     }
3899                     /* Send "new" early, in case this command takes
3900                        a long time to finish, so that we'll be ready
3901                        for the next challenge. */
3902                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3903                     Reset(TRUE, TRUE);
3904                 }
3905 #endif /*ZIPPY*/
3906                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3907                 continue;
3908             }
3909
3910             if (looking_at(buf, &i, "Removing game * from observation") ||
3911                 looking_at(buf, &i, "no longer observing game *") ||
3912                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3913                 if (gameMode == IcsObserving &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       /* icsEngineAnalyze */
3917                       if (appData.icsEngineAnalyze) {
3918                             ExitAnalyzeMode();
3919                             ModeHighlight();
3920                       }
3921                       StopClocks();
3922                       gameMode = IcsIdle;
3923                       ics_gamenum = -1;
3924                       ics_user_moved = FALSE;
3925                   }
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "no longer examining game *")) {
3930                 if (gameMode == IcsExamining &&
3931                     atoi(star_match[0]) == ics_gamenum)
3932                   {
3933                       gameMode = IcsIdle;
3934                       ics_gamenum = -1;
3935                       ics_user_moved = FALSE;
3936                   }
3937                 continue;
3938             }
3939
3940             /* Advance leftover_start past any newlines we find,
3941                so only partial lines can get reparsed */
3942             if (looking_at(buf, &i, "\n")) {
3943                 prevColor = curColor;
3944                 if (curColor != ColorNormal) {
3945                     if (oldi > next_out) {
3946                         SendToPlayer(&buf[next_out], oldi - next_out);
3947                         next_out = oldi;
3948                     }
3949                     Colorize(ColorNormal, FALSE);
3950                     curColor = ColorNormal;
3951                 }
3952                 if (started == STARTED_BOARD) {
3953                     started = STARTED_NONE;
3954                     parse[parse_pos] = NULLCHAR;
3955                     ParseBoard12(parse);
3956                     ics_user_moved = 0;
3957
3958                     /* Send premove here */
3959                     if (appData.premove) {
3960                       char str[MSG_SIZ];
3961                       if (currentMove == 0 &&
3962                           gameMode == IcsPlayingWhite &&
3963                           appData.premoveWhite) {
3964                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                         SendToICS(str);
3968                       } else if (currentMove == 1 &&
3969                                  gameMode == IcsPlayingBlack &&
3970                                  appData.premoveBlack) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (gotPremove) {
3976                         gotPremove = 0;
3977                         ClearPremoveHighlights();
3978                         if (appData.debugMode)
3979                           fprintf(debugFP, "Sending premove:\n");
3980                           UserMoveEvent(premoveFromX, premoveFromY,
3981                                         premoveToX, premoveToY,
3982                                         premovePromoChar);
3983                       }
3984                     }
3985
3986                     /* Usually suppress following prompt */
3987                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3988                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3989                         if (looking_at(buf, &i, "*% ")) {
3990                             savingComment = FALSE;
3991                             suppressKibitz = 0;
3992                         }
3993                     }
3994                     next_out = i;
3995                 } else if (started == STARTED_HOLDINGS) {
3996                     int gamenum;
3997                     char new_piece[MSG_SIZ];
3998                     started = STARTED_NONE;
3999                     parse[parse_pos] = NULLCHAR;
4000                     if (appData.debugMode)
4001                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4002                                                         parse, currentMove);
4003                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4004                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4005                         if (gameInfo.variant == VariantNormal) {
4006                           /* [HGM] We seem to switch variant during a game!
4007                            * Presumably no holdings were displayed, so we have
4008                            * to move the position two files to the right to
4009                            * create room for them!
4010                            */
4011                           VariantClass newVariant;
4012                           switch(gameInfo.boardWidth) { // base guess on board width
4013                                 case 9:  newVariant = VariantShogi; break;
4014                                 case 10: newVariant = VariantGreat; break;
4015                                 default: newVariant = VariantCrazyhouse; break;
4016                           }
4017                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4018                           /* Get a move list just to see the header, which
4019                              will tell us whether this is really bug or zh */
4020                           if (ics_getting_history == H_FALSE) {
4021                             ics_getting_history = H_REQUESTED;
4022                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023                             SendToICS(str);
4024                           }
4025                         }
4026                         new_piece[0] = NULLCHAR;
4027                         sscanf(parse, "game %d white [%s black [%s <- %s",
4028                                &gamenum, white_holding, black_holding,
4029                                new_piece);
4030                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4031                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4032                         /* [HGM] copy holdings to board holdings area */
4033                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4034                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4035                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4036 #if ZIPPY
4037                         if (appData.zippyPlay && first.initDone) {
4038                             ZippyHoldings(white_holding, black_holding,
4039                                           new_piece);
4040                         }
4041 #endif /*ZIPPY*/
4042                         if (tinyLayout || smallLayout) {
4043                             char wh[16], bh[16];
4044                             PackHolding(wh, white_holding);
4045                             PackHolding(bh, black_holding);
4046                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4047                                     gameInfo.white, gameInfo.black);
4048                         } else {
4049                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4050                                     gameInfo.white, white_holding,
4051                                     gameInfo.black, black_holding);
4052                         }
4053                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4054                         DrawPosition(FALSE, boards[currentMove]);
4055                         DisplayTitle(str);
4056                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4057                         sscanf(parse, "game %d white [%s black [%s <- %s",
4058                                &gamenum, white_holding, black_holding,
4059                                new_piece);
4060                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4061                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4062                         /* [HGM] copy holdings to partner-board holdings area */
4063                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4064                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4065                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4066                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4067                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4068                       }
4069                     }
4070                     /* Suppress following prompt */
4071                     if (looking_at(buf, &i, "*% ")) {
4072                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4073                         savingComment = FALSE;
4074                         suppressKibitz = 0;
4075                     }
4076                     next_out = i;
4077                 }
4078                 continue;
4079             }
4080
4081             i++;                /* skip unparsed character and loop back */
4082         }
4083
4084         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4085 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4086 //          SendToPlayer(&buf[next_out], i - next_out);
4087             started != STARTED_HOLDINGS && leftover_start > next_out) {
4088             SendToPlayer(&buf[next_out], leftover_start - next_out);
4089             next_out = i;
4090         }
4091
4092         leftover_len = buf_len - leftover_start;
4093         /* if buffer ends with something we couldn't parse,
4094            reparse it after appending the next read */
4095
4096     } else if (count == 0) {
4097         RemoveInputSource(isr);
4098         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4099     } else {
4100         DisplayFatalError(_("Error reading from ICS"), error, 1);
4101     }
4102 }
4103
4104
4105 /* Board style 12 looks like this:
4106
4107    <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
4108
4109  * The "<12> " is stripped before it gets to this routine.  The two
4110  * trailing 0's (flip state and clock ticking) are later addition, and
4111  * some chess servers may not have them, or may have only the first.
4112  * Additional trailing fields may be added in the future.
4113  */
4114
4115 #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"
4116
4117 #define RELATION_OBSERVING_PLAYED    0
4118 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4119 #define RELATION_PLAYING_MYMOVE      1
4120 #define RELATION_PLAYING_NOTMYMOVE  -1
4121 #define RELATION_EXAMINING           2
4122 #define RELATION_ISOLATED_BOARD     -3
4123 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4124
4125 void
4126 ParseBoard12(string)
4127      char *string;
4128 {
4129     GameMode newGameMode;
4130     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4131     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4132     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4133     char to_play, board_chars[200];
4134     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4135     char black[32], white[32];
4136     Board board;
4137     int prevMove = currentMove;
4138     int ticking = 2;
4139     ChessMove moveType;
4140     int fromX, fromY, toX, toY;
4141     char promoChar;
4142     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4143     char *bookHit = NULL; // [HGM] book
4144     Boolean weird = FALSE, reqFlag = FALSE;
4145
4146     fromX = fromY = toX = toY = -1;
4147
4148     newGame = FALSE;
4149
4150     if (appData.debugMode)
4151       fprintf(debugFP, _("Parsing board: %s\n"), string);
4152
4153     move_str[0] = NULLCHAR;
4154     elapsed_time[0] = NULLCHAR;
4155     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4156         int  i = 0, j;
4157         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4158             if(string[i] == ' ') { ranks++; files = 0; }
4159             else files++;
4160             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4161             i++;
4162         }
4163         for(j = 0; j <i; j++) board_chars[j] = string[j];
4164         board_chars[i] = '\0';
4165         string += i + 1;
4166     }
4167     n = sscanf(string, PATTERN, &to_play, &double_push,
4168                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4169                &gamenum, white, black, &relation, &basetime, &increment,
4170                &white_stren, &black_stren, &white_time, &black_time,
4171                &moveNum, str, elapsed_time, move_str, &ics_flip,
4172                &ticking);
4173
4174     if (n < 21) {
4175         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4176         DisplayError(str, 0);
4177         return;
4178     }
4179
4180     /* Convert the move number to internal form */
4181     moveNum = (moveNum - 1) * 2;
4182     if (to_play == 'B') moveNum++;
4183     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4184       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4185                         0, 1);
4186       return;
4187     }
4188
4189     switch (relation) {
4190       case RELATION_OBSERVING_PLAYED:
4191       case RELATION_OBSERVING_STATIC:
4192         if (gamenum == -1) {
4193             /* Old ICC buglet */
4194             relation = RELATION_OBSERVING_STATIC;
4195         }
4196         newGameMode = IcsObserving;
4197         break;
4198       case RELATION_PLAYING_MYMOVE:
4199       case RELATION_PLAYING_NOTMYMOVE:
4200         newGameMode =
4201           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4202             IcsPlayingWhite : IcsPlayingBlack;
4203         break;
4204       case RELATION_EXAMINING:
4205         newGameMode = IcsExamining;
4206         break;
4207       case RELATION_ISOLATED_BOARD:
4208       default:
4209         /* Just display this board.  If user was doing something else,
4210            we will forget about it until the next board comes. */
4211         newGameMode = IcsIdle;
4212         break;
4213       case RELATION_STARTING_POSITION:
4214         newGameMode = gameMode;
4215         break;
4216     }
4217
4218     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4219          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4220       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4221       char *toSqr;
4222       for (k = 0; k < ranks; k++) {
4223         for (j = 0; j < files; j++)
4224           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4225         if(gameInfo.holdingsWidth > 1) {
4226              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4227              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4228         }
4229       }
4230       CopyBoard(partnerBoard, board);
4231       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4232         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4233         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4235       if(toSqr = strchr(str, '-')) {
4236         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4237         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4239       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4240       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4241       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4243       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4244                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4245       DisplayMessage(partnerStatus, "");
4246         partnerBoardValid = TRUE;
4247       return;
4248     }
4249
4250     /* Modify behavior for initial board display on move listing
4251        of wild games.
4252        */
4253     switch (ics_getting_history) {
4254       case H_FALSE:
4255       case H_REQUESTED:
4256         break;
4257       case H_GOT_REQ_HEADER:
4258       case H_GOT_UNREQ_HEADER:
4259         /* This is the initial position of the current game */
4260         gamenum = ics_gamenum;
4261         moveNum = 0;            /* old ICS bug workaround */
4262         if (to_play == 'B') {
4263           startedFromSetupPosition = TRUE;
4264           blackPlaysFirst = TRUE;
4265           moveNum = 1;
4266           if (forwardMostMove == 0) forwardMostMove = 1;
4267           if (backwardMostMove == 0) backwardMostMove = 1;
4268           if (currentMove == 0) currentMove = 1;
4269         }
4270         newGameMode = gameMode;
4271         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4272         break;
4273       case H_GOT_UNWANTED_HEADER:
4274         /* This is an initial board that we don't want */
4275         return;
4276       case H_GETTING_MOVES:
4277         /* Should not happen */
4278         DisplayError(_("Error gathering move list: extra board"), 0);
4279         ics_getting_history = H_FALSE;
4280         return;
4281     }
4282
4283    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4284                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4285      /* [HGM] We seem to have switched variant unexpectedly
4286       * Try to guess new variant from board size
4287       */
4288           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4289           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4290           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4291           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4292           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4293           if(!weird) newVariant = VariantNormal;
4294           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4295           /* Get a move list just to see the header, which
4296              will tell us whether this is really bug or zh */
4297           if (ics_getting_history == H_FALSE) {
4298             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4299             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4300             SendToICS(str);
4301           }
4302     }
4303
4304     /* Take action if this is the first board of a new game, or of a
4305        different game than is currently being displayed.  */
4306     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4307         relation == RELATION_ISOLATED_BOARD) {
4308
4309         /* Forget the old game and get the history (if any) of the new one */
4310         if (gameMode != BeginningOfGame) {
4311           Reset(TRUE, TRUE);
4312         }
4313         newGame = TRUE;
4314         if (appData.autoRaiseBoard) BoardToTop();
4315         prevMove = -3;
4316         if (gamenum == -1) {
4317             newGameMode = IcsIdle;
4318         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4319                    appData.getMoveList && !reqFlag) {
4320             /* Need to get game history */
4321             ics_getting_history = H_REQUESTED;
4322             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4323             SendToICS(str);
4324         }
4325
4326         /* Initially flip the board to have black on the bottom if playing
4327            black or if the ICS flip flag is set, but let the user change
4328            it with the Flip View button. */
4329         flipView = appData.autoFlipView ?
4330           (newGameMode == IcsPlayingBlack) || ics_flip :
4331           appData.flipView;
4332
4333         /* Done with values from previous mode; copy in new ones */
4334         gameMode = newGameMode;
4335         ModeHighlight();
4336         ics_gamenum = gamenum;
4337         if (gamenum == gs_gamenum) {
4338             int klen = strlen(gs_kind);
4339             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4340             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4341             gameInfo.event = StrSave(str);
4342         } else {
4343             gameInfo.event = StrSave("ICS game");
4344         }
4345         gameInfo.site = StrSave(appData.icsHost);
4346         gameInfo.date = PGNDate();
4347         gameInfo.round = StrSave("-");
4348         gameInfo.white = StrSave(white);
4349         gameInfo.black = StrSave(black);
4350         timeControl = basetime * 60 * 1000;
4351         timeControl_2 = 0;
4352         timeIncrement = increment * 1000;
4353         movesPerSession = 0;
4354         gameInfo.timeControl = TimeControlTagValue();
4355         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4356   if (appData.debugMode) {
4357     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4358     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4359     setbuf(debugFP, NULL);
4360   }
4361
4362         gameInfo.outOfBook = NULL;
4363
4364         /* Do we have the ratings? */
4365         if (strcmp(player1Name, white) == 0 &&
4366             strcmp(player2Name, black) == 0) {
4367             if (appData.debugMode)
4368               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4369                       player1Rating, player2Rating);
4370             gameInfo.whiteRating = player1Rating;
4371             gameInfo.blackRating = player2Rating;
4372         } else if (strcmp(player2Name, white) == 0 &&
4373                    strcmp(player1Name, black) == 0) {
4374             if (appData.debugMode)
4375               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4376                       player2Rating, player1Rating);
4377             gameInfo.whiteRating = player2Rating;
4378             gameInfo.blackRating = player1Rating;
4379         }
4380         player1Name[0] = player2Name[0] = NULLCHAR;
4381
4382         /* Silence shouts if requested */
4383         if (appData.quietPlay &&
4384             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4385             SendToICS(ics_prefix);
4386             SendToICS("set shout 0\n");
4387         }
4388     }
4389
4390     /* Deal with midgame name changes */
4391     if (!newGame) {
4392         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4393             if (gameInfo.white) free(gameInfo.white);
4394             gameInfo.white = StrSave(white);
4395         }
4396         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4397             if (gameInfo.black) free(gameInfo.black);
4398             gameInfo.black = StrSave(black);
4399         }
4400     }
4401
4402     /* Throw away game result if anything actually changes in examine mode */
4403     if (gameMode == IcsExamining && !newGame) {
4404         gameInfo.result = GameUnfinished;
4405         if (gameInfo.resultDetails != NULL) {
4406             free(gameInfo.resultDetails);
4407             gameInfo.resultDetails = NULL;
4408         }
4409     }
4410
4411     /* In pausing && IcsExamining mode, we ignore boards coming
4412        in if they are in a different variation than we are. */
4413     if (pauseExamInvalid) return;
4414     if (pausing && gameMode == IcsExamining) {
4415         if (moveNum <= pauseExamForwardMostMove) {
4416             pauseExamInvalid = TRUE;
4417             forwardMostMove = pauseExamForwardMostMove;
4418             return;
4419         }
4420     }
4421
4422   if (appData.debugMode) {
4423     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4424   }
4425     /* Parse the board */
4426     for (k = 0; k < ranks; k++) {
4427       for (j = 0; j < files; j++)
4428         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429       if(gameInfo.holdingsWidth > 1) {
4430            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432       }
4433     }
4434     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4435       board[5][BOARD_RGHT+1] = WhiteAngel;
4436       board[6][BOARD_RGHT+1] = WhiteMarshall;
4437       board[1][0] = BlackMarshall;
4438       board[2][0] = BlackAngel;
4439       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4440     }
4441     CopyBoard(boards[moveNum], board);
4442     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4443     if (moveNum == 0) {
4444         startedFromSetupPosition =
4445           !CompareBoards(board, initialPosition);
4446         if(startedFromSetupPosition)
4447             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4448     }
4449
4450     /* [HGM] Set castling rights. Take the outermost Rooks,
4451        to make it also work for FRC opening positions. Note that board12
4452        is really defective for later FRC positions, as it has no way to
4453        indicate which Rook can castle if they are on the same side of King.
4454        For the initial position we grant rights to the outermost Rooks,
4455        and remember thos rights, and we then copy them on positions
4456        later in an FRC game. This means WB might not recognize castlings with
4457        Rooks that have moved back to their original position as illegal,
4458        but in ICS mode that is not its job anyway.
4459     */
4460     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4461     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4462
4463         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4464             if(board[0][i] == WhiteRook) j = i;
4465         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4467             if(board[0][i] == WhiteRook) j = i;
4468         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4469         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4470             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4471         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4472         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4473             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4474         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4475
4476         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4477         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4478         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4479             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4480         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4481             if(board[BOARD_HEIGHT-1][k] == bKing)
4482                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4483         if(gameInfo.variant == VariantTwoKings) {
4484             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4485             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4486             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4487         }
4488     } else { int r;
4489         r = boards[moveNum][CASTLING][0] = initialRights[0];
4490         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4491         r = boards[moveNum][CASTLING][1] = initialRights[1];
4492         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4493         r = boards[moveNum][CASTLING][3] = initialRights[3];
4494         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4495         r = boards[moveNum][CASTLING][4] = initialRights[4];
4496         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4497         /* wildcastle kludge: always assume King has rights */
4498         r = boards[moveNum][CASTLING][2] = initialRights[2];
4499         r = boards[moveNum][CASTLING][5] = initialRights[5];
4500     }
4501     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4502     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4503
4504
4505     if (ics_getting_history == H_GOT_REQ_HEADER ||
4506         ics_getting_history == H_GOT_UNREQ_HEADER) {
4507         /* This was an initial position from a move list, not
4508            the current position */
4509         return;
4510     }
4511
4512     /* Update currentMove and known move number limits */
4513     newMove = newGame || moveNum > forwardMostMove;
4514
4515     if (newGame) {
4516         forwardMostMove = backwardMostMove = currentMove = moveNum;
4517         if (gameMode == IcsExamining && moveNum == 0) {
4518           /* Workaround for ICS limitation: we are not told the wild
4519              type when starting to examine a game.  But if we ask for
4520              the move list, the move list header will tell us */
4521             ics_getting_history = H_REQUESTED;
4522             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4523             SendToICS(str);
4524         }
4525     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4526                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4527 #if ZIPPY
4528         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4529         /* [HGM] applied this also to an engine that is silently watching        */
4530         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4531             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4532             gameInfo.variant == currentlyInitializedVariant) {
4533           takeback = forwardMostMove - moveNum;
4534           for (i = 0; i < takeback; i++) {
4535             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4536             SendToProgram("undo\n", &first);
4537           }
4538         }
4539 #endif
4540
4541         forwardMostMove = moveNum;
4542         if (!pausing || currentMove > forwardMostMove)
4543           currentMove = forwardMostMove;
4544     } else {
4545         /* New part of history that is not contiguous with old part */
4546         if (pausing && gameMode == IcsExamining) {
4547             pauseExamInvalid = TRUE;
4548             forwardMostMove = pauseExamForwardMostMove;
4549             return;
4550         }
4551         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4552 #if ZIPPY
4553             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4554                 // [HGM] when we will receive the move list we now request, it will be
4555                 // fed to the engine from the first move on. So if the engine is not
4556                 // in the initial position now, bring it there.
4557                 InitChessProgram(&first, 0);
4558             }
4559 #endif
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564         forwardMostMove = backwardMostMove = currentMove = moveNum;
4565     }
4566
4567     /* Update the clocks */
4568     if (strchr(elapsed_time, '.')) {
4569       /* Time is in ms */
4570       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4571       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4572     } else {
4573       /* Time is in seconds */
4574       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4575       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4576     }
4577
4578
4579 #if ZIPPY
4580     if (appData.zippyPlay && newGame &&
4581         gameMode != IcsObserving && gameMode != IcsIdle &&
4582         gameMode != IcsExamining)
4583       ZippyFirstBoard(moveNum, basetime, increment);
4584 #endif
4585
4586     /* Put the move on the move list, first converting
4587        to canonical algebraic form. */
4588     if (moveNum > 0) {
4589   if (appData.debugMode) {
4590     if (appData.debugMode) { int f = forwardMostMove;
4591         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4592                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4593                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4594     }
4595     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4596     fprintf(debugFP, "moveNum = %d\n", moveNum);
4597     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4598     setbuf(debugFP, NULL);
4599   }
4600         if (moveNum <= backwardMostMove) {
4601             /* We don't know what the board looked like before
4602                this move.  Punt. */
4603           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4604             strcat(parseList[moveNum - 1], " ");
4605             strcat(parseList[moveNum - 1], elapsed_time);
4606             moveList[moveNum - 1][0] = NULLCHAR;
4607         } else if (strcmp(move_str, "none") == 0) {
4608             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4609             /* Again, we don't know what the board looked like;
4610                this is really the start of the game. */
4611             parseList[moveNum - 1][0] = NULLCHAR;
4612             moveList[moveNum - 1][0] = NULLCHAR;
4613             backwardMostMove = moveNum;
4614             startedFromSetupPosition = TRUE;
4615             fromX = fromY = toX = toY = -1;
4616         } else {
4617           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4618           //                 So we parse the long-algebraic move string in stead of the SAN move
4619           int valid; char buf[MSG_SIZ], *prom;
4620
4621           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4622                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4623           // str looks something like "Q/a1-a2"; kill the slash
4624           if(str[1] == '/')
4625             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4626           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4627           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4628                 strcat(buf, prom); // long move lacks promo specification!
4629           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4630                 if(appData.debugMode)
4631                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4632                 safeStrCpy(move_str, buf, MSG_SIZ);
4633           }
4634           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4635                                 &fromX, &fromY, &toX, &toY, &promoChar)
4636                || ParseOneMove(buf, moveNum - 1, &moveType,
4637                                 &fromX, &fromY, &toX, &toY, &promoChar);
4638           // end of long SAN patch
4639           if (valid) {
4640             (void) CoordsToAlgebraic(boards[moveNum - 1],
4641                                      PosFlags(moveNum - 1),
4642                                      fromY, fromX, toY, toX, promoChar,
4643                                      parseList[moveNum-1]);
4644             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4645               case MT_NONE:
4646               case MT_STALEMATE:
4647               default:
4648                 break;
4649               case MT_CHECK:
4650                 if(gameInfo.variant != VariantShogi)
4651                     strcat(parseList[moveNum - 1], "+");
4652                 break;
4653               case MT_CHECKMATE:
4654               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4655                 strcat(parseList[moveNum - 1], "#");
4656                 break;
4657             }
4658             strcat(parseList[moveNum - 1], " ");
4659             strcat(parseList[moveNum - 1], elapsed_time);
4660             /* currentMoveString is set as a side-effect of ParseOneMove */
4661             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4662             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4663             strcat(moveList[moveNum - 1], "\n");
4664
4665             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4666                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4667               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4668                 ChessSquare old, new = boards[moveNum][k][j];
4669                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4670                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4671                   if(old == new) continue;
4672                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4673                   else if(new == WhiteWazir || new == BlackWazir) {
4674                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4675                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4676                       else boards[moveNum][k][j] = old; // preserve type of Gold
4677                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4678                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4679               }
4680           } else {
4681             /* Move from ICS was illegal!?  Punt. */
4682             if (appData.debugMode) {
4683               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4684               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4685             }
4686             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4687             strcat(parseList[moveNum - 1], " ");
4688             strcat(parseList[moveNum - 1], elapsed_time);
4689             moveList[moveNum - 1][0] = NULLCHAR;
4690             fromX = fromY = toX = toY = -1;
4691           }
4692         }
4693   if (appData.debugMode) {
4694     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4695     setbuf(debugFP, NULL);
4696   }
4697
4698 #if ZIPPY
4699         /* Send move to chess program (BEFORE animating it). */
4700         if (appData.zippyPlay && !newGame && newMove &&
4701            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4702
4703             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4704                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4705                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4706                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4707                             move_str);
4708                     DisplayError(str, 0);
4709                 } else {
4710                     if (first.sendTime) {
4711                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4712                     }
4713                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4714                     if (firstMove && !bookHit) {
4715                         firstMove = FALSE;
4716                         if (first.useColors) {
4717                           SendToProgram(gameMode == IcsPlayingWhite ?
4718                                         "white\ngo\n" :
4719                                         "black\ngo\n", &first);
4720                         } else {
4721                           SendToProgram("go\n", &first);
4722                         }
4723                         first.maybeThinking = TRUE;
4724                     }
4725                 }
4726             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4727               if (moveList[moveNum - 1][0] == NULLCHAR) {
4728                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4729                 DisplayError(str, 0);
4730               } else {
4731                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4732                 SendMoveToProgram(moveNum - 1, &first);
4733               }
4734             }
4735         }
4736 #endif
4737     }
4738
4739     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4740         /* If move comes from a remote source, animate it.  If it
4741            isn't remote, it will have already been animated. */
4742         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4743             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4744         }
4745         if (!pausing && appData.highlightLastMove) {
4746             SetHighlights(fromX, fromY, toX, toY);
4747         }
4748     }
4749
4750     /* Start the clocks */
4751     whiteFlag = blackFlag = FALSE;
4752     appData.clockMode = !(basetime == 0 && increment == 0);
4753     if (ticking == 0) {
4754       ics_clock_paused = TRUE;
4755       StopClocks();
4756     } else if (ticking == 1) {
4757       ics_clock_paused = FALSE;
4758     }
4759     if (gameMode == IcsIdle ||
4760         relation == RELATION_OBSERVING_STATIC ||
4761         relation == RELATION_EXAMINING ||
4762         ics_clock_paused)
4763       DisplayBothClocks();
4764     else
4765       StartClocks();
4766
4767     /* Display opponents and material strengths */
4768     if (gameInfo.variant != VariantBughouse &&
4769         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4770         if (tinyLayout || smallLayout) {
4771             if(gameInfo.variant == VariantNormal)
4772               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4773                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4774                     basetime, increment);
4775             else
4776               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4777                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4778                     basetime, increment, (int) gameInfo.variant);
4779         } else {
4780             if(gameInfo.variant == VariantNormal)
4781               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4782                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4783                     basetime, increment);
4784             else
4785               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4786                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4787                     basetime, increment, VariantName(gameInfo.variant));
4788         }
4789         DisplayTitle(str);
4790   if (appData.debugMode) {
4791     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4792   }
4793     }
4794
4795
4796     /* Display the board */
4797     if (!pausing && !appData.noGUI) {
4798
4799       if (appData.premove)
4800           if (!gotPremove ||
4801              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4802              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4803               ClearPremoveHighlights();
4804
4805       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4806         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4807       DrawPosition(j, boards[currentMove]);
4808
4809       DisplayMove(moveNum - 1);
4810       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4811             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4812               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4813         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4814       }
4815     }
4816
4817     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4818 #if ZIPPY
4819     if(bookHit) { // [HGM] book: simulate book reply
4820         static char bookMove[MSG_SIZ]; // a bit generous?
4821
4822         programStats.nodes = programStats.depth = programStats.time =
4823         programStats.score = programStats.got_only_move = 0;
4824         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4825
4826         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4827         strcat(bookMove, bookHit);
4828         HandleMachineMove(bookMove, &first);
4829     }
4830 #endif
4831 }
4832
4833 void
4834 GetMoveListEvent()
4835 {
4836     char buf[MSG_SIZ];
4837     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4838         ics_getting_history = H_REQUESTED;
4839         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4840         SendToICS(buf);
4841     }
4842 }
4843
4844 void
4845 AnalysisPeriodicEvent(force)
4846      int force;
4847 {
4848     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4849          && !force) || !appData.periodicUpdates)
4850       return;
4851
4852     /* Send . command to Crafty to collect stats */
4853     SendToProgram(".\n", &first);
4854
4855     /* Don't send another until we get a response (this makes
4856        us stop sending to old Crafty's which don't understand
4857        the "." command (sending illegal cmds resets node count & time,
4858        which looks bad)) */
4859     programStats.ok_to_send = 0;
4860 }
4861
4862 void ics_update_width(new_width)
4863         int new_width;
4864 {
4865         ics_printf("set width %d\n", new_width);
4866 }
4867
4868 void
4869 SendMoveToProgram(moveNum, cps)
4870      int moveNum;
4871      ChessProgramState *cps;
4872 {
4873     char buf[MSG_SIZ];
4874
4875     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4876         // null move in variant where engine does not understand it (for analysis purposes)
4877         SendBoard(cps, moveNum + 1); // send position after move in stead.
4878         return;
4879     }
4880     if (cps->useUsermove) {
4881       SendToProgram("usermove ", cps);
4882     }
4883     if (cps->useSAN) {
4884       char *space;
4885       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4886         int len = space - parseList[moveNum];
4887         memcpy(buf, parseList[moveNum], len);
4888         buf[len++] = '\n';
4889         buf[len] = NULLCHAR;
4890       } else {
4891         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4892       }
4893       SendToProgram(buf, cps);
4894     } else {
4895       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4896         AlphaRank(moveList[moveNum], 4);
4897         SendToProgram(moveList[moveNum], cps);
4898         AlphaRank(moveList[moveNum], 4); // and back
4899       } else
4900       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4901        * the engine. It would be nice to have a better way to identify castle
4902        * moves here. */
4903       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4904                                                                          && cps->useOOCastle) {
4905         int fromX = moveList[moveNum][0] - AAA;
4906         int fromY = moveList[moveNum][1] - ONE;
4907         int toX = moveList[moveNum][2] - AAA;
4908         int toY = moveList[moveNum][3] - ONE;
4909         if((boards[moveNum][fromY][fromX] == WhiteKing
4910             && boards[moveNum][toY][toX] == WhiteRook)
4911            || (boards[moveNum][fromY][fromX] == BlackKing
4912                && boards[moveNum][toY][toX] == BlackRook)) {
4913           if(toX > fromX) SendToProgram("O-O\n", cps);
4914           else SendToProgram("O-O-O\n", cps);
4915         }
4916         else SendToProgram(moveList[moveNum], cps);
4917       } else
4918       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4919         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4920           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4921           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4922                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4923         } else
4924           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4925                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4926         SendToProgram(buf, cps);
4927       }
4928       else SendToProgram(moveList[moveNum], cps);
4929       /* End of additions by Tord */
4930     }
4931
4932     /* [HGM] setting up the opening has brought engine in force mode! */
4933     /*       Send 'go' if we are in a mode where machine should play. */
4934     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4935         (gameMode == TwoMachinesPlay   ||
4936 #if ZIPPY
4937          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4938 #endif
4939          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4940         SendToProgram("go\n", cps);
4941   if (appData.debugMode) {
4942     fprintf(debugFP, "(extra)\n");
4943   }
4944     }
4945     setboardSpoiledMachineBlack = 0;
4946 }
4947
4948 void
4949 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4950      ChessMove moveType;
4951      int fromX, fromY, toX, toY;
4952      char promoChar;
4953 {
4954     char user_move[MSG_SIZ];
4955     char suffix[4];
4956
4957     if(gameInfo.variant == VariantSChess && promoChar) {
4958         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4959         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4960     } else suffix[0] = NULLCHAR;
4961
4962     switch (moveType) {
4963       default:
4964         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4965                 (int)moveType, fromX, fromY, toX, toY);
4966         DisplayError(user_move + strlen("say "), 0);
4967         break;
4968       case WhiteKingSideCastle:
4969       case BlackKingSideCastle:
4970       case WhiteQueenSideCastleWild:
4971       case BlackQueenSideCastleWild:
4972       /* PUSH Fabien */
4973       case WhiteHSideCastleFR:
4974       case BlackHSideCastleFR:
4975       /* POP Fabien */
4976         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4977         break;
4978       case WhiteQueenSideCastle:
4979       case BlackQueenSideCastle:
4980       case WhiteKingSideCastleWild:
4981       case BlackKingSideCastleWild:
4982       /* PUSH Fabien */
4983       case WhiteASideCastleFR:
4984       case BlackASideCastleFR:
4985       /* POP Fabien */
4986         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4987         break;
4988       case WhiteNonPromotion:
4989       case BlackNonPromotion:
4990         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4991         break;
4992       case WhitePromotion:
4993       case BlackPromotion:
4994         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 PieceToChar(WhiteFerz));
4998         else if(gameInfo.variant == VariantGreat)
4999           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5000                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5001                 PieceToChar(WhiteMan));
5002         else
5003           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5005                 promoChar);
5006         break;
5007       case WhiteDrop:
5008       case BlackDrop:
5009       drop:
5010         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5011                  ToUpper(PieceToChar((ChessSquare) fromX)),
5012                  AAA + toX, ONE + toY);
5013         break;
5014       case IllegalMove:  /* could be a variant we don't quite understand */
5015         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5016       case NormalMove:
5017       case WhiteCapturesEnPassant:
5018       case BlackCapturesEnPassant:
5019         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5020                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5021         break;
5022     }
5023     SendToICS(user_move);
5024     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5025         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5026 }
5027
5028 void
5029 UploadGameEvent()
5030 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5031     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5032     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5033     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5034       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5035       return;
5036     }
5037     if(gameMode != IcsExamining) { // is this ever not the case?
5038         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5039
5040         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5041           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5042         } else { // on FICS we must first go to general examine mode
5043           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5044         }
5045         if(gameInfo.variant != VariantNormal) {
5046             // try figure out wild number, as xboard names are not always valid on ICS
5047             for(i=1; i<=36; i++) {
5048               snprintf(buf, MSG_SIZ, "wild/%d", i);
5049                 if(StringToVariant(buf) == gameInfo.variant) break;
5050             }
5051             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5052             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5053             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5054         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5055         SendToICS(ics_prefix);
5056         SendToICS(buf);
5057         if(startedFromSetupPosition || backwardMostMove != 0) {
5058           fen = PositionToFEN(backwardMostMove, NULL);
5059           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5060             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5061             SendToICS(buf);
5062           } else { // FICS: everything has to set by separate bsetup commands
5063             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5064             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5065             SendToICS(buf);
5066             if(!WhiteOnMove(backwardMostMove)) {
5067                 SendToICS("bsetup tomove black\n");
5068             }
5069             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5070             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5071             SendToICS(buf);
5072             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5073             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5074             SendToICS(buf);
5075             i = boards[backwardMostMove][EP_STATUS];
5076             if(i >= 0) { // set e.p.
5077               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5078                 SendToICS(buf);
5079             }
5080             bsetup++;
5081           }
5082         }
5083       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5084             SendToICS("bsetup done\n"); // switch to normal examining.
5085     }
5086     for(i = backwardMostMove; i<last; i++) {
5087         char buf[20];
5088         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5089         SendToICS(buf);
5090     }
5091     SendToICS(ics_prefix);
5092     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5093 }
5094
5095 void
5096 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5097      int rf, ff, rt, ft;
5098      char promoChar;
5099      char move[7];
5100 {
5101     if (rf == DROP_RANK) {
5102       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5103       sprintf(move, "%c@%c%c\n",
5104                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5105     } else {
5106         if (promoChar == 'x' || promoChar == NULLCHAR) {
5107           sprintf(move, "%c%c%c%c\n",
5108                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5109         } else {
5110             sprintf(move, "%c%c%c%c%c\n",
5111                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5112         }
5113     }
5114 }
5115
5116 void
5117 ProcessICSInitScript(f)
5118      FILE *f;
5119 {
5120     char buf[MSG_SIZ];
5121
5122     while (fgets(buf, MSG_SIZ, f)) {
5123         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5124     }
5125
5126     fclose(f);
5127 }
5128
5129
5130 static int lastX, lastY, selectFlag, dragging;
5131
5132 void
5133 Sweep(int step)
5134 {
5135     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5136     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5137     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5138     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5139     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5140     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5141     do {
5142         promoSweep -= step;
5143         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5144         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5145         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5146         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5147         if(!step) step = -1;
5148     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5149             appData.testLegality && (promoSweep == king ||
5150             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5151     ChangeDragPiece(promoSweep);
5152 }
5153
5154 int PromoScroll(int x, int y)
5155 {
5156   int step = 0;
5157
5158   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5159   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5160   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5161   if(!step) return FALSE;
5162   lastX = x; lastY = y;
5163   if((promoSweep < BlackPawn) == flipView) step = -step;
5164   if(step > 0) selectFlag = 1;
5165   if(!selectFlag) Sweep(step);
5166   return FALSE;
5167 }
5168
5169 void
5170 NextPiece(int step)
5171 {
5172     ChessSquare piece = boards[currentMove][toY][toX];
5173     do {
5174         pieceSweep -= step;
5175         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5176         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5177         if(!step) step = -1;
5178     } while(PieceToChar(pieceSweep) == '.');
5179     boards[currentMove][toY][toX] = pieceSweep;
5180     DrawPosition(FALSE, boards[currentMove]);
5181     boards[currentMove][toY][toX] = piece;
5182 }
5183 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5184 void
5185 AlphaRank(char *move, int n)
5186 {
5187 //    char *p = move, c; int x, y;
5188
5189     if (appData.debugMode) {
5190         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5191     }
5192
5193     if(move[1]=='*' &&
5194        move[2]>='0' && move[2]<='9' &&
5195        move[3]>='a' && move[3]<='x'    ) {
5196         move[1] = '@';
5197         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5199     } else
5200     if(move[0]>='0' && move[0]<='9' &&
5201        move[1]>='a' && move[1]<='x' &&
5202        move[2]>='0' && move[2]<='9' &&
5203        move[3]>='a' && move[3]<='x'    ) {
5204         /* input move, Shogi -> normal */
5205         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5206         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5207         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5209     } else
5210     if(move[1]=='@' &&
5211        move[3]>='0' && move[3]<='9' &&
5212        move[2]>='a' && move[2]<='x'    ) {
5213         move[1] = '*';
5214         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5215         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5216     } else
5217     if(
5218        move[0]>='a' && move[0]<='x' &&
5219        move[3]>='0' && move[3]<='9' &&
5220        move[2]>='a' && move[2]<='x'    ) {
5221          /* output move, normal -> Shogi */
5222         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5223         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5224         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5225         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5226         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5227     }
5228     if (appData.debugMode) {
5229         fprintf(debugFP, "   out = '%s'\n", move);
5230     }
5231 }
5232
5233 char yy_textstr[8000];
5234
5235 /* Parser for moves from gnuchess, ICS, or user typein box */
5236 Boolean
5237 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5238      char *move;
5239      int moveNum;
5240      ChessMove *moveType;
5241      int *fromX, *fromY, *toX, *toY;
5242      char *promoChar;
5243 {
5244     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5245
5246     switch (*moveType) {
5247       case WhitePromotion:
5248       case BlackPromotion:
5249       case WhiteNonPromotion:
5250       case BlackNonPromotion:
5251       case NormalMove:
5252       case WhiteCapturesEnPassant:
5253       case BlackCapturesEnPassant:
5254       case WhiteKingSideCastle:
5255       case WhiteQueenSideCastle:
5256       case BlackKingSideCastle:
5257       case BlackQueenSideCastle:
5258       case WhiteKingSideCastleWild:
5259       case WhiteQueenSideCastleWild:
5260       case BlackKingSideCastleWild:
5261       case BlackQueenSideCastleWild:
5262       /* Code added by Tord: */
5263       case WhiteHSideCastleFR:
5264       case WhiteASideCastleFR:
5265       case BlackHSideCastleFR:
5266       case BlackASideCastleFR:
5267       /* End of code added by Tord */
5268       case IllegalMove:         /* bug or odd chess variant */
5269         *fromX = currentMoveString[0] - AAA;
5270         *fromY = currentMoveString[1] - ONE;
5271         *toX = currentMoveString[2] - AAA;
5272         *toY = currentMoveString[3] - ONE;
5273         *promoChar = currentMoveString[4];
5274         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5275             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5276     if (appData.debugMode) {
5277         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5278     }
5279             *fromX = *fromY = *toX = *toY = 0;
5280             return FALSE;
5281         }
5282         if (appData.testLegality) {
5283           return (*moveType != IllegalMove);
5284         } else {
5285           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5286                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5287         }
5288
5289       case WhiteDrop:
5290       case BlackDrop:
5291         *fromX = *moveType == WhiteDrop ?
5292           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5293           (int) CharToPiece(ToLower(currentMoveString[0]));
5294         *fromY = DROP_RANK;
5295         *toX = currentMoveString[2] - AAA;
5296         *toY = currentMoveString[3] - ONE;
5297         *promoChar = NULLCHAR;
5298         return TRUE;
5299
5300       case AmbiguousMove:
5301       case ImpossibleMove:
5302       case EndOfFile:
5303       case ElapsedTime:
5304       case Comment:
5305       case PGNTag:
5306       case NAG:
5307       case WhiteWins:
5308       case BlackWins:
5309       case GameIsDrawn:
5310       default:
5311     if (appData.debugMode) {
5312         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5313     }
5314         /* bug? */
5315         *fromX = *fromY = *toX = *toY = 0;
5316         *promoChar = NULLCHAR;
5317         return FALSE;
5318     }
5319 }
5320
5321 Boolean pushed = FALSE;
5322 char *lastParseAttempt;
5323
5324 void
5325 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5326 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5327   int fromX, fromY, toX, toY; char promoChar;
5328   ChessMove moveType;
5329   Boolean valid;
5330   int nr = 0;
5331
5332   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5333     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5334     pushed = TRUE;
5335   }
5336   endPV = forwardMostMove;
5337   do {
5338     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5339     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5340     lastParseAttempt = pv;
5341     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5342 if(appData.debugMode){
5343 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);
5344 }
5345     if(!valid && nr == 0 &&
5346        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5347         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5348         // Hande case where played move is different from leading PV move
5349         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5350         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5351         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5352         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5353           endPV += 2; // if position different, keep this
5354           moveList[endPV-1][0] = fromX + AAA;
5355           moveList[endPV-1][1] = fromY + ONE;
5356           moveList[endPV-1][2] = toX + AAA;
5357           moveList[endPV-1][3] = toY + ONE;
5358           parseList[endPV-1][0] = NULLCHAR;
5359           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5360         }
5361       }
5362     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5363     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5364     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5365     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5366         valid++; // allow comments in PV
5367         continue;
5368     }
5369     nr++;
5370     if(endPV+1 > framePtr) break; // no space, truncate
5371     if(!valid) break;
5372     endPV++;
5373     CopyBoard(boards[endPV], boards[endPV-1]);
5374     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5375     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5376     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5377     CoordsToAlgebraic(boards[endPV - 1],
5378                              PosFlags(endPV - 1),
5379                              fromY, fromX, toY, toX, promoChar,
5380                              parseList[endPV - 1]);
5381   } while(valid);
5382   if(atEnd == 2) return; // used hidden, for PV conversion
5383   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5384   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5385   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5386                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5387   DrawPosition(TRUE, boards[currentMove]);
5388 }
5389
5390 int
5391 MultiPV(ChessProgramState *cps)
5392 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5393         int i;
5394         for(i=0; i<cps->nrOptions; i++)
5395             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5396                 return i;
5397         return -1;
5398 }
5399
5400 Boolean
5401 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5402 {
5403         int startPV, multi, lineStart, origIndex = index;
5404         char *p, buf2[MSG_SIZ];
5405
5406         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5407         lastX = x; lastY = y;
5408         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5409         lineStart = startPV = index;
5410         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5411         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5412         index = startPV;
5413         do{ while(buf[index] && buf[index] != '\n') index++;
5414         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5415         buf[index] = 0;
5416         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5417                 int n = first.option[multi].value;
5418                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5419                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5420                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5421                 first.option[multi].value = n;
5422                 *start = *end = 0;
5423                 return FALSE;
5424         }
5425         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5426         *start = startPV; *end = index-1;
5427         return TRUE;
5428 }
5429
5430 char *
5431 PvToSAN(char *pv)
5432 {
5433         static char buf[10*MSG_SIZ];
5434         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5435         *buf = NULLCHAR;
5436         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5437         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5438         for(i = forwardMostMove; i<endPV; i++){
5439             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5440             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5441             k += strlen(buf+k);
5442         }
5443         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5444         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5445         endPV = savedEnd;
5446         return buf;
5447 }
5448
5449 Boolean
5450 LoadPV(int x, int y)
5451 { // called on right mouse click to load PV
5452   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5453   lastX = x; lastY = y;
5454   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5455   return TRUE;
5456 }
5457
5458 void
5459 UnLoadPV()
5460 {
5461   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5462   if(endPV < 0) return;
5463   endPV = -1;
5464   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5465         Boolean saveAnimate = appData.animate;
5466         if(pushed) {
5467             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5468                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5469             } else storedGames--; // abandon shelved tail of original game
5470         }
5471         pushed = FALSE;
5472         forwardMostMove = currentMove;
5473         currentMove = oldFMM;
5474         appData.animate = FALSE;
5475         ToNrEvent(forwardMostMove);
5476         appData.animate = saveAnimate;
5477   }
5478   currentMove = forwardMostMove;
5479   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5480   ClearPremoveHighlights();
5481   DrawPosition(TRUE, boards[currentMove]);
5482 }
5483
5484 void
5485 MovePV(int x, int y, int h)
5486 { // step through PV based on mouse coordinates (called on mouse move)
5487   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5488
5489   // we must somehow check if right button is still down (might be released off board!)
5490   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5491   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5492   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5493   if(!step) return;
5494   lastX = x; lastY = y;
5495
5496   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5497   if(endPV < 0) return;
5498   if(y < margin) step = 1; else
5499   if(y > h - margin) step = -1;
5500   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5501   currentMove += step;
5502   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5503   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5504                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5505   DrawPosition(FALSE, boards[currentMove]);
5506 }
5507
5508
5509 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5510 // All positions will have equal probability, but the current method will not provide a unique
5511 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5512 #define DARK 1
5513 #define LITE 2
5514 #define ANY 3
5515
5516 int squaresLeft[4];
5517 int piecesLeft[(int)BlackPawn];
5518 int seed, nrOfShuffles;
5519
5520 void GetPositionNumber()
5521 {       // sets global variable seed
5522         int i;
5523
5524         seed = appData.defaultFrcPosition;
5525         if(seed < 0) { // randomize based on time for negative FRC position numbers
5526                 for(i=0; i<50; i++) seed += random();
5527                 seed = random() ^ random() >> 8 ^ random() << 8;
5528                 if(seed<0) seed = -seed;
5529         }
5530 }
5531
5532 int put(Board board, int pieceType, int rank, int n, int shade)
5533 // put the piece on the (n-1)-th empty squares of the given shade
5534 {
5535         int i;
5536
5537         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5538                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5539                         board[rank][i] = (ChessSquare) pieceType;
5540                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5541                         squaresLeft[ANY]--;
5542                         piecesLeft[pieceType]--;
5543                         return i;
5544                 }
5545         }
5546         return -1;
5547 }
5548
5549
5550 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5551 // calculate where the next piece goes, (any empty square), and put it there
5552 {
5553         int i;
5554
5555         i = seed % squaresLeft[shade];
5556         nrOfShuffles *= squaresLeft[shade];
5557         seed /= squaresLeft[shade];
5558         put(board, pieceType, rank, i, shade);
5559 }
5560
5561 void AddTwoPieces(Board board, int pieceType, int rank)
5562 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5563 {
5564         int i, n=squaresLeft[ANY], j=n-1, k;
5565
5566         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5567         i = seed % k;  // pick one
5568         nrOfShuffles *= k;
5569         seed /= k;
5570         while(i >= j) i -= j--;
5571         j = n - 1 - j; i += j;
5572         put(board, pieceType, rank, j, ANY);
5573         put(board, pieceType, rank, i, ANY);
5574 }
5575
5576 void SetUpShuffle(Board board, int number)
5577 {
5578         int i, p, first=1;
5579
5580         GetPositionNumber(); nrOfShuffles = 1;
5581
5582         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5583         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5584         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5585
5586         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5587
5588         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5589             p = (int) board[0][i];
5590             if(p < (int) BlackPawn) piecesLeft[p] ++;
5591             board[0][i] = EmptySquare;
5592         }
5593
5594         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5595             // shuffles restricted to allow normal castling put KRR first
5596             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5597                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5598             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5599                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5600             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5601                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5602             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5603                 put(board, WhiteRook, 0, 0, ANY);
5604             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5605         }
5606
5607         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5608             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5609             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5610                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5611                 while(piecesLeft[p] >= 2) {
5612                     AddOnePiece(board, p, 0, LITE);
5613                     AddOnePiece(board, p, 0, DARK);
5614                 }
5615                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5616             }
5617
5618         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5619             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5620             // but we leave King and Rooks for last, to possibly obey FRC restriction
5621             if(p == (int)WhiteRook) continue;
5622             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5623             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5624         }
5625
5626         // now everything is placed, except perhaps King (Unicorn) and Rooks
5627
5628         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5629             // Last King gets castling rights
5630             while(piecesLeft[(int)WhiteUnicorn]) {
5631                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5632                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5633             }
5634
5635             while(piecesLeft[(int)WhiteKing]) {
5636                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5637                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5638             }
5639
5640
5641         } else {
5642             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5643             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5644         }
5645
5646         // Only Rooks can be left; simply place them all
5647         while(piecesLeft[(int)WhiteRook]) {
5648                 i = put(board, WhiteRook, 0, 0, ANY);
5649                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5650                         if(first) {
5651                                 first=0;
5652                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5653                         }
5654                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5655                 }
5656         }
5657         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5658             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5659         }
5660
5661         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5662 }
5663
5664 int SetCharTable( char *table, const char * map )
5665 /* [HGM] moved here from winboard.c because of its general usefulness */
5666 /*       Basically a safe strcpy that uses the last character as King */
5667 {
5668     int result = FALSE; int NrPieces;
5669
5670     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5671                     && NrPieces >= 12 && !(NrPieces&1)) {
5672         int i; /* [HGM] Accept even length from 12 to 34 */
5673
5674         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5675         for( i=0; i<NrPieces/2-1; i++ ) {
5676             table[i] = map[i];
5677             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5678         }
5679         table[(int) WhiteKing]  = map[NrPieces/2-1];
5680         table[(int) BlackKing]  = map[NrPieces-1];
5681
5682         result = TRUE;
5683     }
5684
5685     return result;
5686 }
5687
5688 void Prelude(Board board)
5689 {       // [HGM] superchess: random selection of exo-pieces
5690         int i, j, k; ChessSquare p;
5691         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5692
5693         GetPositionNumber(); // use FRC position number
5694
5695         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5696             SetCharTable(pieceToChar, appData.pieceToCharTable);
5697             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5698                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5699         }
5700
5701         j = seed%4;                 seed /= 4;
5702         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5703         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5704         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5705         j = seed%3 + (seed%3 >= j); seed /= 3;
5706         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5709         j = seed%3;                 seed /= 3;
5710         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5711         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5712         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5713         j = seed%2 + (seed%2 >= j); seed /= 2;
5714         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5715         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5716         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5717         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5718         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5719         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5720         put(board, exoPieces[0],    0, 0, ANY);
5721         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5722 }
5723
5724 void
5725 InitPosition(redraw)
5726      int redraw;
5727 {
5728     ChessSquare (* pieces)[BOARD_FILES];
5729     int i, j, pawnRow, overrule,
5730     oldx = gameInfo.boardWidth,
5731     oldy = gameInfo.boardHeight,
5732     oldh = gameInfo.holdingsWidth;
5733     static int oldv;
5734
5735     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5736
5737     /* [AS] Initialize pv info list [HGM] and game status */
5738     {
5739         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5740             pvInfoList[i].depth = 0;
5741             boards[i][EP_STATUS] = EP_NONE;
5742             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5743         }
5744
5745         initialRulePlies = 0; /* 50-move counter start */
5746
5747         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5748         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5749     }
5750
5751
5752     /* [HGM] logic here is completely changed. In stead of full positions */
5753     /* the initialized data only consist of the two backranks. The switch */
5754     /* selects which one we will use, which is than copied to the Board   */
5755     /* initialPosition, which for the rest is initialized by Pawns and    */
5756     /* empty squares. This initial position is then copied to boards[0],  */
5757     /* possibly after shuffling, so that it remains available.            */
5758
5759     gameInfo.holdingsWidth = 0; /* default board sizes */
5760     gameInfo.boardWidth    = 8;
5761     gameInfo.boardHeight   = 8;
5762     gameInfo.holdingsSize  = 0;
5763     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5764     for(i=0; i<BOARD_FILES-2; i++)
5765       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5766     initialPosition[EP_STATUS] = EP_NONE;
5767     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5768     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5769          SetCharTable(pieceNickName, appData.pieceNickNames);
5770     else SetCharTable(pieceNickName, "............");
5771     pieces = FIDEArray;
5772
5773     switch (gameInfo.variant) {
5774     case VariantFischeRandom:
5775       shuffleOpenings = TRUE;
5776     default:
5777       break;
5778     case VariantShatranj:
5779       pieces = ShatranjArray;
5780       nrCastlingRights = 0;
5781       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5782       break;
5783     case VariantMakruk:
5784       pieces = makrukArray;
5785       nrCastlingRights = 0;
5786       startedFromSetupPosition = TRUE;
5787       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5788       break;
5789     case VariantTwoKings:
5790       pieces = twoKingsArray;
5791       break;
5792     case VariantGrand:
5793       pieces = GrandArray;
5794       nrCastlingRights = 0;
5795       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5796       gameInfo.boardWidth = 10;
5797       gameInfo.boardHeight = 10;
5798       gameInfo.holdingsSize = 7;
5799       break;
5800     case VariantCapaRandom:
5801       shuffleOpenings = TRUE;
5802     case VariantCapablanca:
5803       pieces = CapablancaArray;
5804       gameInfo.boardWidth = 10;
5805       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5806       break;
5807     case VariantGothic:
5808       pieces = GothicArray;
5809       gameInfo.boardWidth = 10;
5810       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5811       break;
5812     case VariantSChess:
5813       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5814       gameInfo.holdingsSize = 7;
5815       break;
5816     case VariantJanus:
5817       pieces = JanusArray;
5818       gameInfo.boardWidth = 10;
5819       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5820       nrCastlingRights = 6;
5821         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5822         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5823         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5824         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5825         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5826         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5827       break;
5828     case VariantFalcon:
5829       pieces = FalconArray;
5830       gameInfo.boardWidth = 10;
5831       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5832       break;
5833     case VariantXiangqi:
5834       pieces = XiangqiArray;
5835       gameInfo.boardWidth  = 9;
5836       gameInfo.boardHeight = 10;
5837       nrCastlingRights = 0;
5838       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5839       break;
5840     case VariantShogi:
5841       pieces = ShogiArray;
5842       gameInfo.boardWidth  = 9;
5843       gameInfo.boardHeight = 9;
5844       gameInfo.holdingsSize = 7;
5845       nrCastlingRights = 0;
5846       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5847       break;
5848     case VariantCourier:
5849       pieces = CourierArray;
5850       gameInfo.boardWidth  = 12;
5851       nrCastlingRights = 0;
5852       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5853       break;
5854     case VariantKnightmate:
5855       pieces = KnightmateArray;
5856       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5857       break;
5858     case VariantSpartan:
5859       pieces = SpartanArray;
5860       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5861       break;
5862     case VariantFairy:
5863       pieces = fairyArray;
5864       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5865       break;
5866     case VariantGreat:
5867       pieces = GreatArray;
5868       gameInfo.boardWidth = 10;
5869       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5870       gameInfo.holdingsSize = 8;
5871       break;
5872     case VariantSuper:
5873       pieces = FIDEArray;
5874       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5875       gameInfo.holdingsSize = 8;
5876       startedFromSetupPosition = TRUE;
5877       break;
5878     case VariantCrazyhouse:
5879     case VariantBughouse:
5880       pieces = FIDEArray;
5881       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5882       gameInfo.holdingsSize = 5;
5883       break;
5884     case VariantWildCastle:
5885       pieces = FIDEArray;
5886       /* !!?shuffle with kings guaranteed to be on d or e file */
5887       shuffleOpenings = 1;
5888       break;
5889     case VariantNoCastle:
5890       pieces = FIDEArray;
5891       nrCastlingRights = 0;
5892       /* !!?unconstrained back-rank shuffle */
5893       shuffleOpenings = 1;
5894       break;
5895     }
5896
5897     overrule = 0;
5898     if(appData.NrFiles >= 0) {
5899         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5900         gameInfo.boardWidth = appData.NrFiles;
5901     }
5902     if(appData.NrRanks >= 0) {
5903         gameInfo.boardHeight = appData.NrRanks;
5904     }
5905     if(appData.holdingsSize >= 0) {
5906         i = appData.holdingsSize;
5907         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5908         gameInfo.holdingsSize = i;
5909     }
5910     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5911     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5912         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5913
5914     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5915     if(pawnRow < 1) pawnRow = 1;
5916     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5917
5918     /* User pieceToChar list overrules defaults */
5919     if(appData.pieceToCharTable != NULL)
5920         SetCharTable(pieceToChar, appData.pieceToCharTable);
5921
5922     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5923
5924         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5925             s = (ChessSquare) 0; /* account holding counts in guard band */
5926         for( i=0; i<BOARD_HEIGHT; i++ )
5927             initialPosition[i][j] = s;
5928
5929         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5930         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5931         initialPosition[pawnRow][j] = WhitePawn;
5932         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5933         if(gameInfo.variant == VariantXiangqi) {
5934             if(j&1) {
5935                 initialPosition[pawnRow][j] =
5936                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5937                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5938                    initialPosition[2][j] = WhiteCannon;
5939                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5940                 }
5941             }
5942         }
5943         if(gameInfo.variant == VariantGrand) {
5944             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5945                initialPosition[0][j] = WhiteRook;
5946                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5947             }
5948         }
5949         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5950     }
5951     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5952
5953             j=BOARD_LEFT+1;
5954             initialPosition[1][j] = WhiteBishop;
5955             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5956             j=BOARD_RGHT-2;
5957             initialPosition[1][j] = WhiteRook;
5958             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5959     }
5960
5961     if( nrCastlingRights == -1) {
5962         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5963         /*       This sets default castling rights from none to normal corners   */
5964         /* Variants with other castling rights must set them themselves above    */
5965         nrCastlingRights = 6;
5966
5967         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5968         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5969         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5970         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5971         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5972         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5973      }
5974
5975      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5976      if(gameInfo.variant == VariantGreat) { // promotion commoners
5977         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5978         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5979         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5980         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5981      }
5982      if( gameInfo.variant == VariantSChess ) {
5983       initialPosition[1][0] = BlackMarshall;
5984       initialPosition[2][0] = BlackAngel;
5985       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5986       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5987       initialPosition[1][1] = initialPosition[2][1] = 
5988       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5989      }
5990   if (appData.debugMode) {
5991     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5992   }
5993     if(shuffleOpenings) {
5994         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5995         startedFromSetupPosition = TRUE;
5996     }
5997     if(startedFromPositionFile) {
5998       /* [HGM] loadPos: use PositionFile for every new game */
5999       CopyBoard(initialPosition, filePosition);
6000       for(i=0; i<nrCastlingRights; i++)
6001           initialRights[i] = filePosition[CASTLING][i];
6002       startedFromSetupPosition = TRUE;
6003     }
6004
6005     CopyBoard(boards[0], initialPosition);
6006
6007     if(oldx != gameInfo.boardWidth ||
6008        oldy != gameInfo.boardHeight ||
6009        oldv != gameInfo.variant ||
6010        oldh != gameInfo.holdingsWidth
6011                                          )
6012             InitDrawingSizes(-2 ,0);
6013
6014     oldv = gameInfo.variant;
6015     if (redraw)
6016       DrawPosition(TRUE, boards[currentMove]);
6017 }
6018
6019 void
6020 SendBoard(cps, moveNum)
6021      ChessProgramState *cps;
6022      int moveNum;
6023 {
6024     char message[MSG_SIZ];
6025
6026     if (cps->useSetboard) {
6027       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6028       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6029       SendToProgram(message, cps);
6030       free(fen);
6031
6032     } else {
6033       ChessSquare *bp;
6034       int i, j, left=0, right=BOARD_WIDTH;
6035       /* Kludge to set black to move, avoiding the troublesome and now
6036        * deprecated "black" command.
6037        */
6038       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6039         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6040
6041       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6042
6043       SendToProgram("edit\n", cps);
6044       SendToProgram("#\n", cps);
6045       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6046         bp = &boards[moveNum][i][left];
6047         for (j = left; j < right; j++, bp++) {
6048           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6049           if ((int) *bp < (int) BlackPawn) {
6050             if(j == BOARD_RGHT+1)
6051                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6052             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6053             if(message[0] == '+' || message[0] == '~') {
6054               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6055                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6056                         AAA + j, ONE + i);
6057             }
6058             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6059                 message[1] = BOARD_RGHT   - 1 - j + '1';
6060                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6061             }
6062             SendToProgram(message, cps);
6063           }
6064         }
6065       }
6066
6067       SendToProgram("c\n", cps);
6068       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6069         bp = &boards[moveNum][i][left];
6070         for (j = left; j < right; j++, bp++) {
6071           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6072           if (((int) *bp != (int) EmptySquare)
6073               && ((int) *bp >= (int) BlackPawn)) {
6074             if(j == BOARD_LEFT-2)
6075                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6076             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6077                     AAA + j, ONE + i);
6078             if(message[0] == '+' || message[0] == '~') {
6079               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6080                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6081                         AAA + j, ONE + i);
6082             }
6083             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6084                 message[1] = BOARD_RGHT   - 1 - j + '1';
6085                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6086             }
6087             SendToProgram(message, cps);
6088           }
6089         }
6090       }
6091
6092       SendToProgram(".\n", cps);
6093     }
6094     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6095 }
6096
6097 ChessSquare
6098 DefaultPromoChoice(int white)
6099 {
6100     ChessSquare result;
6101     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6102         result = WhiteFerz; // no choice
6103     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6104         result= WhiteKing; // in Suicide Q is the last thing we want
6105     else if(gameInfo.variant == VariantSpartan)
6106         result = white ? WhiteQueen : WhiteAngel;
6107     else result = WhiteQueen;
6108     if(!white) result = WHITE_TO_BLACK result;
6109     return result;
6110 }
6111
6112 static int autoQueen; // [HGM] oneclick
6113
6114 int
6115 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6116 {
6117     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6118     /* [HGM] add Shogi promotions */
6119     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6120     ChessSquare piece;
6121     ChessMove moveType;
6122     Boolean premove;
6123
6124     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6125     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6126
6127     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6128       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6129         return FALSE;
6130
6131     piece = boards[currentMove][fromY][fromX];
6132     if(gameInfo.variant == VariantShogi) {
6133         promotionZoneSize = BOARD_HEIGHT/3;
6134         highestPromotingPiece = (int)WhiteFerz;
6135     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6136         promotionZoneSize = 3;
6137     }
6138
6139     // Treat Lance as Pawn when it is not representing Amazon
6140     if(gameInfo.variant != VariantSuper) {
6141         if(piece == WhiteLance) piece = WhitePawn; else
6142         if(piece == BlackLance) piece = BlackPawn;
6143     }
6144
6145     // next weed out all moves that do not touch the promotion zone at all
6146     if((int)piece >= BlackPawn) {
6147         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6148              return FALSE;
6149         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6150     } else {
6151         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6152            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6153     }
6154
6155     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6156
6157     // weed out mandatory Shogi promotions
6158     if(gameInfo.variant == VariantShogi) {
6159         if(piece >= BlackPawn) {
6160             if(toY == 0 && piece == BlackPawn ||
6161                toY == 0 && piece == BlackQueen ||
6162                toY <= 1 && piece == BlackKnight) {
6163                 *promoChoice = '+';
6164                 return FALSE;
6165             }
6166         } else {
6167             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6168                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6169                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6170                 *promoChoice = '+';
6171                 return FALSE;
6172             }
6173         }
6174     }
6175
6176     // weed out obviously illegal Pawn moves
6177     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6178         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6179         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6180         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6181         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6182         // note we are not allowed to test for valid (non-)capture, due to premove
6183     }
6184
6185     // we either have a choice what to promote to, or (in Shogi) whether to promote
6186     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6187         *promoChoice = PieceToChar(BlackFerz);  // no choice
6188         return FALSE;
6189     }
6190     // no sense asking what we must promote to if it is going to explode...
6191     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6192         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6193         return FALSE;
6194     }
6195     // give caller the default choice even if we will not make it
6196     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6197     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6198     if(        sweepSelect && gameInfo.variant != VariantGreat
6199                            && gameInfo.variant != VariantGrand
6200                            && gameInfo.variant != VariantSuper) return FALSE;
6201     if(autoQueen) return FALSE; // predetermined
6202
6203     // suppress promotion popup on illegal moves that are not premoves
6204     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6205               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6206     if(appData.testLegality && !premove) {
6207         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6208                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6209         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6210             return FALSE;
6211     }
6212
6213     return TRUE;
6214 }
6215
6216 int
6217 InPalace(row, column)
6218      int row, column;
6219 {   /* [HGM] for Xiangqi */
6220     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6221          column < (BOARD_WIDTH + 4)/2 &&
6222          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6223     return FALSE;
6224 }
6225
6226 int
6227 PieceForSquare (x, y)
6228      int x;
6229      int y;
6230 {
6231   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6232      return -1;
6233   else
6234      return boards[currentMove][y][x];
6235 }
6236
6237 int
6238 OKToStartUserMove(x, y)
6239      int x, y;
6240 {
6241     ChessSquare from_piece;
6242     int white_piece;
6243
6244     if (matchMode) return FALSE;
6245     if (gameMode == EditPosition) return TRUE;
6246
6247     if (x >= 0 && y >= 0)
6248       from_piece = boards[currentMove][y][x];
6249     else
6250       from_piece = EmptySquare;
6251
6252     if (from_piece == EmptySquare) return FALSE;
6253
6254     white_piece = (int)from_piece >= (int)WhitePawn &&
6255       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6256
6257     switch (gameMode) {
6258       case AnalyzeFile:
6259       case TwoMachinesPlay:
6260       case EndOfGame:
6261         return FALSE;
6262
6263       case IcsObserving:
6264       case IcsIdle:
6265         return FALSE;
6266
6267       case MachinePlaysWhite:
6268       case IcsPlayingBlack:
6269         if (appData.zippyPlay) return FALSE;
6270         if (white_piece) {
6271             DisplayMoveError(_("You are playing Black"));
6272             return FALSE;
6273         }
6274         break;
6275
6276       case MachinePlaysBlack:
6277       case IcsPlayingWhite:
6278         if (appData.zippyPlay) return FALSE;
6279         if (!white_piece) {
6280             DisplayMoveError(_("You are playing White"));
6281             return FALSE;
6282         }
6283         break;
6284
6285       case PlayFromGameFile:
6286             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6287       case EditGame:
6288         if (!white_piece && WhiteOnMove(currentMove)) {
6289             DisplayMoveError(_("It is White's turn"));
6290             return FALSE;
6291         }
6292         if (white_piece && !WhiteOnMove(currentMove)) {
6293             DisplayMoveError(_("It is Black's turn"));
6294             return FALSE;
6295         }
6296         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6297             /* Editing correspondence game history */
6298             /* Could disallow this or prompt for confirmation */
6299             cmailOldMove = -1;
6300         }
6301         break;
6302
6303       case BeginningOfGame:
6304         if (appData.icsActive) return FALSE;
6305         if (!appData.noChessProgram) {
6306             if (!white_piece) {
6307                 DisplayMoveError(_("You are playing White"));
6308                 return FALSE;
6309             }
6310         }
6311         break;
6312
6313       case Training:
6314         if (!white_piece && WhiteOnMove(currentMove)) {
6315             DisplayMoveError(_("It is White's turn"));
6316             return FALSE;
6317         }
6318         if (white_piece && !WhiteOnMove(currentMove)) {
6319             DisplayMoveError(_("It is Black's turn"));
6320             return FALSE;
6321         }
6322         break;
6323
6324       default:
6325       case IcsExamining:
6326         break;
6327     }
6328     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6329         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6330         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6331         && gameMode != AnalyzeFile && gameMode != Training) {
6332         DisplayMoveError(_("Displayed position is not current"));
6333         return FALSE;
6334     }
6335     return TRUE;
6336 }
6337
6338 Boolean
6339 OnlyMove(int *x, int *y, Boolean captures) {
6340     DisambiguateClosure cl;
6341     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6342     switch(gameMode) {
6343       case MachinePlaysBlack:
6344       case IcsPlayingWhite:
6345       case BeginningOfGame:
6346         if(!WhiteOnMove(currentMove)) return FALSE;
6347         break;
6348       case MachinePlaysWhite:
6349       case IcsPlayingBlack:
6350         if(WhiteOnMove(currentMove)) return FALSE;
6351         break;
6352       case EditGame:
6353         break;
6354       default:
6355         return FALSE;
6356     }
6357     cl.pieceIn = EmptySquare;
6358     cl.rfIn = *y;
6359     cl.ffIn = *x;
6360     cl.rtIn = -1;
6361     cl.ftIn = -1;
6362     cl.promoCharIn = NULLCHAR;
6363     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6364     if( cl.kind == NormalMove ||
6365         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6366         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6367         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6368       fromX = cl.ff;
6369       fromY = cl.rf;
6370       *x = cl.ft;
6371       *y = cl.rt;
6372       return TRUE;
6373     }
6374     if(cl.kind != ImpossibleMove) return FALSE;
6375     cl.pieceIn = EmptySquare;
6376     cl.rfIn = -1;
6377     cl.ffIn = -1;
6378     cl.rtIn = *y;
6379     cl.ftIn = *x;
6380     cl.promoCharIn = NULLCHAR;
6381     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6382     if( cl.kind == NormalMove ||
6383         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6384         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6385         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6386       fromX = cl.ff;
6387       fromY = cl.rf;
6388       *x = cl.ft;
6389       *y = cl.rt;
6390       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6391       return TRUE;
6392     }
6393     return FALSE;
6394 }
6395
6396 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6397 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6398 int lastLoadGameUseList = FALSE;
6399 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6400 ChessMove lastLoadGameStart = EndOfFile;
6401
6402 void
6403 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6404      int fromX, fromY, toX, toY;
6405      int promoChar;
6406 {
6407     ChessMove moveType;
6408     ChessSquare pdown, pup;
6409
6410     /* Check if the user is playing in turn.  This is complicated because we
6411        let the user "pick up" a piece before it is his turn.  So the piece he
6412        tried to pick up may have been captured by the time he puts it down!
6413        Therefore we use the color the user is supposed to be playing in this
6414        test, not the color of the piece that is currently on the starting
6415        square---except in EditGame mode, where the user is playing both
6416        sides; fortunately there the capture race can't happen.  (It can
6417        now happen in IcsExamining mode, but that's just too bad.  The user
6418        will get a somewhat confusing message in that case.)
6419        */
6420
6421     switch (gameMode) {
6422       case AnalyzeFile:
6423       case TwoMachinesPlay:
6424       case EndOfGame:
6425       case IcsObserving:
6426       case IcsIdle:
6427         /* We switched into a game mode where moves are not accepted,
6428            perhaps while the mouse button was down. */
6429         return;
6430
6431       case MachinePlaysWhite:
6432         /* User is moving for Black */
6433         if (WhiteOnMove(currentMove)) {
6434             DisplayMoveError(_("It is White's turn"));
6435             return;
6436         }
6437         break;
6438
6439       case MachinePlaysBlack:
6440         /* User is moving for White */
6441         if (!WhiteOnMove(currentMove)) {
6442             DisplayMoveError(_("It is Black's turn"));
6443             return;
6444         }
6445         break;
6446
6447       case PlayFromGameFile:
6448             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6449       case EditGame:
6450       case IcsExamining:
6451       case BeginningOfGame:
6452       case AnalyzeMode:
6453       case Training:
6454         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6455         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6456             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6457             /* User is moving for Black */
6458             if (WhiteOnMove(currentMove)) {
6459                 DisplayMoveError(_("It is White's turn"));
6460                 return;
6461             }
6462         } else {
6463             /* User is moving for White */
6464             if (!WhiteOnMove(currentMove)) {
6465                 DisplayMoveError(_("It is Black's turn"));
6466                 return;
6467             }
6468         }
6469         break;
6470
6471       case IcsPlayingBlack:
6472         /* User is moving for Black */
6473         if (WhiteOnMove(currentMove)) {
6474             if (!appData.premove) {
6475                 DisplayMoveError(_("It is White's turn"));
6476             } else if (toX >= 0 && toY >= 0) {
6477                 premoveToX = toX;
6478                 premoveToY = toY;
6479                 premoveFromX = fromX;
6480                 premoveFromY = fromY;
6481                 premovePromoChar = promoChar;
6482                 gotPremove = 1;
6483                 if (appData.debugMode)
6484                     fprintf(debugFP, "Got premove: fromX %d,"
6485                             "fromY %d, toX %d, toY %d\n",
6486                             fromX, fromY, toX, toY);
6487             }
6488             return;
6489         }
6490         break;
6491
6492       case IcsPlayingWhite:
6493         /* User is moving for White */
6494         if (!WhiteOnMove(currentMove)) {
6495             if (!appData.premove) {
6496                 DisplayMoveError(_("It is Black's turn"));
6497             } else if (toX >= 0 && toY >= 0) {
6498                 premoveToX = toX;
6499                 premoveToY = toY;
6500                 premoveFromX = fromX;
6501                 premoveFromY = fromY;
6502                 premovePromoChar = promoChar;
6503                 gotPremove = 1;
6504                 if (appData.debugMode)
6505                     fprintf(debugFP, "Got premove: fromX %d,"
6506                             "fromY %d, toX %d, toY %d\n",
6507                             fromX, fromY, toX, toY);
6508             }
6509             return;
6510         }
6511         break;
6512
6513       default:
6514         break;
6515
6516       case EditPosition:
6517         /* EditPosition, empty square, or different color piece;
6518            click-click move is possible */
6519         if (toX == -2 || toY == -2) {
6520             boards[0][fromY][fromX] = EmptySquare;
6521             DrawPosition(FALSE, boards[currentMove]);
6522             return;
6523         } else if (toX >= 0 && toY >= 0) {
6524             boards[0][toY][toX] = boards[0][fromY][fromX];
6525             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6526                 if(boards[0][fromY][0] != EmptySquare) {
6527                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6528                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6529                 }
6530             } else
6531             if(fromX == BOARD_RGHT+1) {
6532                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6533                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6534                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6535                 }
6536             } else
6537             boards[0][fromY][fromX] = EmptySquare;
6538             DrawPosition(FALSE, boards[currentMove]);
6539             return;
6540         }
6541         return;
6542     }
6543
6544     if(toX < 0 || toY < 0) return;
6545     pdown = boards[currentMove][fromY][fromX];
6546     pup = boards[currentMove][toY][toX];
6547
6548     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6549     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6550          if( pup != EmptySquare ) return;
6551          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6552            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6553                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6554            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6555            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6556            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6557            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6558          fromY = DROP_RANK;
6559     }
6560
6561     /* [HGM] always test for legality, to get promotion info */
6562     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6563                                          fromY, fromX, toY, toX, promoChar);
6564
6565     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6566
6567     /* [HGM] but possibly ignore an IllegalMove result */
6568     if (appData.testLegality) {
6569         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6570             DisplayMoveError(_("Illegal move"));
6571             return;
6572         }
6573     }
6574
6575     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6576 }
6577
6578 /* Common tail of UserMoveEvent and DropMenuEvent */
6579 int
6580 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6581      ChessMove moveType;
6582      int fromX, fromY, toX, toY;
6583      /*char*/int promoChar;
6584 {
6585     char *bookHit = 0;
6586
6587     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6588         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6589         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6590         if(WhiteOnMove(currentMove)) {
6591             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6592         } else {
6593             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6594         }
6595     }
6596
6597     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6598        move type in caller when we know the move is a legal promotion */
6599     if(moveType == NormalMove && promoChar)
6600         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6601
6602     /* [HGM] <popupFix> The following if has been moved here from
6603        UserMoveEvent(). Because it seemed to belong here (why not allow
6604        piece drops in training games?), and because it can only be
6605        performed after it is known to what we promote. */
6606     if (gameMode == Training) {
6607       /* compare the move played on the board to the next move in the
6608        * game. If they match, display the move and the opponent's response.
6609        * If they don't match, display an error message.
6610        */
6611       int saveAnimate;
6612       Board testBoard;
6613       CopyBoard(testBoard, boards[currentMove]);
6614       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6615
6616       if (CompareBoards(testBoard, boards[currentMove+1])) {
6617         ForwardInner(currentMove+1);
6618
6619         /* Autoplay the opponent's response.
6620          * if appData.animate was TRUE when Training mode was entered,
6621          * the response will be animated.
6622          */
6623         saveAnimate = appData.animate;
6624         appData.animate = animateTraining;
6625         ForwardInner(currentMove+1);
6626         appData.animate = saveAnimate;
6627
6628         /* check for the end of the game */
6629         if (currentMove >= forwardMostMove) {
6630           gameMode = PlayFromGameFile;
6631           ModeHighlight();
6632           SetTrainingModeOff();
6633           DisplayInformation(_("End of game"));
6634         }
6635       } else {
6636         DisplayError(_("Incorrect move"), 0);
6637       }
6638       return 1;
6639     }
6640
6641   /* Ok, now we know that the move is good, so we can kill
6642      the previous line in Analysis Mode */
6643   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6644                                 && currentMove < forwardMostMove) {
6645     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6646     else forwardMostMove = currentMove;
6647   }
6648
6649   /* If we need the chess program but it's dead, restart it */
6650   ResurrectChessProgram();
6651
6652   /* A user move restarts a paused game*/
6653   if (pausing)
6654     PauseEvent();
6655
6656   thinkOutput[0] = NULLCHAR;
6657
6658   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6659
6660   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6661     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6662     return 1;
6663   }
6664
6665   if (gameMode == BeginningOfGame) {
6666     if (appData.noChessProgram) {
6667       gameMode = EditGame;
6668       SetGameInfo();
6669     } else {
6670       char buf[MSG_SIZ];
6671       gameMode = MachinePlaysBlack;
6672       StartClocks();
6673       SetGameInfo();
6674       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6675       DisplayTitle(buf);
6676       if (first.sendName) {
6677         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6678         SendToProgram(buf, &first);
6679       }
6680       StartClocks();
6681     }
6682     ModeHighlight();
6683   }
6684
6685   /* Relay move to ICS or chess engine */
6686   if (appData.icsActive) {
6687     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6688         gameMode == IcsExamining) {
6689       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6690         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6691         SendToICS("draw ");
6692         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6693       }
6694       // also send plain move, in case ICS does not understand atomic claims
6695       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6696       ics_user_moved = 1;
6697     }
6698   } else {
6699     if (first.sendTime && (gameMode == BeginningOfGame ||
6700                            gameMode == MachinePlaysWhite ||
6701                            gameMode == MachinePlaysBlack)) {
6702       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6703     }
6704     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6705          // [HGM] book: if program might be playing, let it use book
6706         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6707         first.maybeThinking = TRUE;
6708     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6709         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6710         SendBoard(&first, currentMove+1);
6711     } else SendMoveToProgram(forwardMostMove-1, &first);
6712     if (currentMove == cmailOldMove + 1) {
6713       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6714     }
6715   }
6716
6717   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6718
6719   switch (gameMode) {
6720   case EditGame:
6721     if(appData.testLegality)
6722     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6723     case MT_NONE:
6724     case MT_CHECK:
6725       break;
6726     case MT_CHECKMATE:
6727     case MT_STAINMATE:
6728       if (WhiteOnMove(currentMove)) {
6729         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6730       } else {
6731         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6732       }
6733       break;
6734     case MT_STALEMATE:
6735       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6736       break;
6737     }
6738     break;
6739
6740   case MachinePlaysBlack:
6741   case MachinePlaysWhite:
6742     /* disable certain menu options while machine is thinking */
6743     SetMachineThinkingEnables();
6744     break;
6745
6746   default:
6747     break;
6748   }
6749
6750   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6751   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6752
6753   if(bookHit) { // [HGM] book: simulate book reply
6754         static char bookMove[MSG_SIZ]; // a bit generous?
6755
6756         programStats.nodes = programStats.depth = programStats.time =
6757         programStats.score = programStats.got_only_move = 0;
6758         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6759
6760         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6761         strcat(bookMove, bookHit);
6762         HandleMachineMove(bookMove, &first);
6763   }
6764   return 1;
6765 }
6766
6767 void
6768 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6769      Board board;
6770      int flags;
6771      ChessMove kind;
6772      int rf, ff, rt, ft;
6773      VOIDSTAR closure;
6774 {
6775     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6776     Markers *m = (Markers *) closure;
6777     if(rf == fromY && ff == fromX)
6778         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6779                          || kind == WhiteCapturesEnPassant
6780                          || kind == BlackCapturesEnPassant);
6781     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6782 }
6783
6784 void
6785 MarkTargetSquares(int clear)
6786 {
6787   int x, y;
6788   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6789      !appData.testLegality || gameMode == EditPosition) return;
6790   if(clear) {
6791     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6792   } else {
6793     int capt = 0;
6794     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6795     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6796       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6797       if(capt)
6798       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6799     }
6800   }
6801   DrawPosition(TRUE, NULL);
6802 }
6803
6804 int
6805 Explode(Board board, int fromX, int fromY, int toX, int toY)
6806 {
6807     if(gameInfo.variant == VariantAtomic &&
6808        (board[toY][toX] != EmptySquare ||                     // capture?
6809         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6810                          board[fromY][fromX] == BlackPawn   )
6811       )) {
6812         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6813         return TRUE;
6814     }
6815     return FALSE;
6816 }
6817
6818 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6819
6820 int CanPromote(ChessSquare piece, int y)
6821 {
6822         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6823         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6824         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6825            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6826            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6827                                                   gameInfo.variant == VariantMakruk) return FALSE;
6828         return (piece == BlackPawn && y == 1 ||
6829                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6830                 piece == BlackLance && y == 1 ||
6831                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6832 }
6833
6834 void LeftClick(ClickType clickType, int xPix, int yPix)
6835 {
6836     int x, y;
6837     Boolean saveAnimate;
6838     static int second = 0, promotionChoice = 0, clearFlag = 0;
6839     char promoChoice = NULLCHAR;
6840     ChessSquare piece;
6841
6842     if(appData.seekGraph && appData.icsActive && loggedOn &&
6843         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6844         SeekGraphClick(clickType, xPix, yPix, 0);
6845         return;
6846     }
6847
6848     if (clickType == Press) ErrorPopDown();
6849
6850     x = EventToSquare(xPix, BOARD_WIDTH);
6851     y = EventToSquare(yPix, BOARD_HEIGHT);
6852     if (!flipView && y >= 0) {
6853         y = BOARD_HEIGHT - 1 - y;
6854     }
6855     if (flipView && x >= 0) {
6856         x = BOARD_WIDTH - 1 - x;
6857     }
6858
6859     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6860         defaultPromoChoice = promoSweep;
6861         promoSweep = EmptySquare;   // terminate sweep
6862         promoDefaultAltered = TRUE;
6863         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6864     }
6865
6866     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6867         if(clickType == Release) return; // ignore upclick of click-click destination
6868         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6869         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6870         if(gameInfo.holdingsWidth &&
6871                 (WhiteOnMove(currentMove)
6872                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6873                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6874             // click in right holdings, for determining promotion piece
6875             ChessSquare p = boards[currentMove][y][x];
6876             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6877             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6878             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6879                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6880                 fromX = fromY = -1;
6881                 return;
6882             }
6883         }
6884         DrawPosition(FALSE, boards[currentMove]);
6885         return;
6886     }
6887
6888     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6889     if(clickType == Press
6890             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6891               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6892               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6893         return;
6894
6895     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6896         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6897
6898     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6899         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6900                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6901         defaultPromoChoice = DefaultPromoChoice(side);
6902     }
6903
6904     autoQueen = appData.alwaysPromoteToQueen;
6905
6906     if (fromX == -1) {
6907       int originalY = y;
6908       gatingPiece = EmptySquare;
6909       if (clickType != Press) {
6910         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6911             DragPieceEnd(xPix, yPix); dragging = 0;
6912             DrawPosition(FALSE, NULL);
6913         }
6914         return;
6915       }
6916       fromX = x; fromY = y; toX = toY = -1;
6917       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6918          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6919          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6920             /* First square */
6921             if (OKToStartUserMove(fromX, fromY)) {
6922                 second = 0;
6923                 MarkTargetSquares(0);
6924                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6925                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6926                     promoSweep = defaultPromoChoice;
6927                     selectFlag = 0; lastX = xPix; lastY = yPix;
6928                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6929                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6930                 }
6931                 if (appData.highlightDragging) {
6932                     SetHighlights(fromX, fromY, -1, -1);
6933                 }
6934             } else fromX = fromY = -1;
6935             return;
6936         }
6937     }
6938
6939     /* fromX != -1 */
6940     if (clickType == Press && gameMode != EditPosition) {
6941         ChessSquare fromP;
6942         ChessSquare toP;
6943         int frc;
6944
6945         // ignore off-board to clicks
6946         if(y < 0 || x < 0) return;
6947
6948         /* Check if clicking again on the same color piece */
6949         fromP = boards[currentMove][fromY][fromX];
6950         toP = boards[currentMove][y][x];
6951         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6952         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6953              WhitePawn <= toP && toP <= WhiteKing &&
6954              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6955              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6956             (BlackPawn <= fromP && fromP <= BlackKing &&
6957              BlackPawn <= toP && toP <= BlackKing &&
6958              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6959              !(fromP == BlackKing && toP == BlackRook && frc))) {
6960             /* Clicked again on same color piece -- changed his mind */
6961             second = (x == fromX && y == fromY);
6962             promoDefaultAltered = FALSE;
6963             MarkTargetSquares(1);
6964            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6965             if (appData.highlightDragging) {
6966                 SetHighlights(x, y, -1, -1);
6967             } else {
6968                 ClearHighlights();
6969             }
6970             if (OKToStartUserMove(x, y)) {
6971                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6972                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6973                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6974                  gatingPiece = boards[currentMove][fromY][fromX];
6975                 else gatingPiece = EmptySquare;
6976                 fromX = x;
6977                 fromY = y; dragging = 1;
6978                 MarkTargetSquares(0);
6979                 DragPieceBegin(xPix, yPix, FALSE);
6980                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6981                     promoSweep = defaultPromoChoice;
6982                     selectFlag = 0; lastX = xPix; lastY = yPix;
6983                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6984                 }
6985             }
6986            }
6987            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6988            second = FALSE; 
6989         }
6990         // ignore clicks on holdings
6991         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6992     }
6993
6994     if (clickType == Release && x == fromX && y == fromY) {
6995         DragPieceEnd(xPix, yPix); dragging = 0;
6996         if(clearFlag) {
6997             // a deferred attempt to click-click move an empty square on top of a piece
6998             boards[currentMove][y][x] = EmptySquare;
6999             ClearHighlights();
7000             DrawPosition(FALSE, boards[currentMove]);
7001             fromX = fromY = -1; clearFlag = 0;
7002             return;
7003         }
7004         if (appData.animateDragging) {
7005             /* Undo animation damage if any */
7006             DrawPosition(FALSE, NULL);
7007         }
7008         if (second) {
7009             /* Second up/down in same square; just abort move */
7010             second = 0;
7011             fromX = fromY = -1;
7012             gatingPiece = EmptySquare;
7013             ClearHighlights();
7014             gotPremove = 0;
7015             ClearPremoveHighlights();
7016         } else {
7017             /* First upclick in same square; start click-click mode */
7018             SetHighlights(x, y, -1, -1);
7019         }
7020         return;
7021     }
7022
7023     clearFlag = 0;
7024
7025     /* we now have a different from- and (possibly off-board) to-square */
7026     /* Completed move */
7027     toX = x;
7028     toY = y;
7029     saveAnimate = appData.animate;
7030     MarkTargetSquares(1);
7031     if (clickType == Press) {
7032         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7033             // must be Edit Position mode with empty-square selected
7034             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7035             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7036             return;
7037         }
7038         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7039             ChessSquare piece = boards[currentMove][fromY][fromX];
7040             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7041             promoSweep = defaultPromoChoice;
7042             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7043             selectFlag = 0; lastX = xPix; lastY = yPix;
7044             Sweep(0); // Pawn that is going to promote: preview promotion piece
7045             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7046             DrawPosition(FALSE, boards[currentMove]);
7047             return;
7048         }
7049         /* Finish clickclick move */
7050         if (appData.animate || appData.highlightLastMove) {
7051             SetHighlights(fromX, fromY, toX, toY);
7052         } else {
7053             ClearHighlights();
7054         }
7055     } else {
7056         /* Finish drag move */
7057         if (appData.highlightLastMove) {
7058             SetHighlights(fromX, fromY, toX, toY);
7059         } else {
7060             ClearHighlights();
7061         }
7062         DragPieceEnd(xPix, yPix); dragging = 0;
7063         /* Don't animate move and drag both */
7064         appData.animate = FALSE;
7065     }
7066
7067     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7068     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7069         ChessSquare piece = boards[currentMove][fromY][fromX];
7070         if(gameMode == EditPosition && piece != EmptySquare &&
7071            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7072             int n;
7073
7074             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7075                 n = PieceToNumber(piece - (int)BlackPawn);
7076                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7077                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7078                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7079             } else
7080             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7081                 n = PieceToNumber(piece);
7082                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7083                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7084                 boards[currentMove][n][BOARD_WIDTH-2]++;
7085             }
7086             boards[currentMove][fromY][fromX] = EmptySquare;
7087         }
7088         ClearHighlights();
7089         fromX = fromY = -1;
7090         DrawPosition(TRUE, boards[currentMove]);
7091         return;
7092     }
7093
7094     // off-board moves should not be highlighted
7095     if(x < 0 || y < 0) ClearHighlights();
7096
7097     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7098
7099     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7100         SetHighlights(fromX, fromY, toX, toY);
7101         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7102             // [HGM] super: promotion to captured piece selected from holdings
7103             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7104             promotionChoice = TRUE;
7105             // kludge follows to temporarily execute move on display, without promoting yet
7106             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7107             boards[currentMove][toY][toX] = p;
7108             DrawPosition(FALSE, boards[currentMove]);
7109             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7110             boards[currentMove][toY][toX] = q;
7111             DisplayMessage("Click in holdings to choose piece", "");
7112             return;
7113         }
7114         PromotionPopUp();
7115     } else {
7116         int oldMove = currentMove;
7117         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7118         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7119         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7120         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7121            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7122             DrawPosition(TRUE, boards[currentMove]);
7123         fromX = fromY = -1;
7124     }
7125     appData.animate = saveAnimate;
7126     if (appData.animate || appData.animateDragging) {
7127         /* Undo animation damage if needed */
7128         DrawPosition(FALSE, NULL);
7129     }
7130 }
7131
7132 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7133 {   // front-end-free part taken out of PieceMenuPopup
7134     int whichMenu; int xSqr, ySqr;
7135
7136     if(seekGraphUp) { // [HGM] seekgraph
7137         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7138         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7139         return -2;
7140     }
7141
7142     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7143          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7144         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7145         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7146         if(action == Press)   {
7147             originalFlip = flipView;
7148             flipView = !flipView; // temporarily flip board to see game from partners perspective
7149             DrawPosition(TRUE, partnerBoard);
7150             DisplayMessage(partnerStatus, "");
7151             partnerUp = TRUE;
7152         } else if(action == Release) {
7153             flipView = originalFlip;
7154             DrawPosition(TRUE, boards[currentMove]);
7155             partnerUp = FALSE;
7156         }
7157         return -2;
7158     }
7159
7160     xSqr = EventToSquare(x, BOARD_WIDTH);
7161     ySqr = EventToSquare(y, BOARD_HEIGHT);
7162     if (action == Release) {
7163         if(pieceSweep != EmptySquare) {
7164             EditPositionMenuEvent(pieceSweep, toX, toY);
7165             pieceSweep = EmptySquare;
7166         } else UnLoadPV(); // [HGM] pv
7167     }
7168     if (action != Press) return -2; // return code to be ignored
7169     switch (gameMode) {
7170       case IcsExamining:
7171         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7172       case EditPosition:
7173         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7174         if (xSqr < 0 || ySqr < 0) return -1;
7175         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7176         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7177         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7178         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7179         NextPiece(0);
7180         return 2; // grab
7181       case IcsObserving:
7182         if(!appData.icsEngineAnalyze) return -1;
7183       case IcsPlayingWhite:
7184       case IcsPlayingBlack:
7185         if(!appData.zippyPlay) goto noZip;
7186       case AnalyzeMode:
7187       case AnalyzeFile:
7188       case MachinePlaysWhite:
7189       case MachinePlaysBlack:
7190       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7191         if (!appData.dropMenu) {
7192           LoadPV(x, y);
7193           return 2; // flag front-end to grab mouse events
7194         }
7195         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7196            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7197       case EditGame:
7198       noZip:
7199         if (xSqr < 0 || ySqr < 0) return -1;
7200         if (!appData.dropMenu || appData.testLegality &&
7201             gameInfo.variant != VariantBughouse &&
7202             gameInfo.variant != VariantCrazyhouse) return -1;
7203         whichMenu = 1; // drop menu
7204         break;
7205       default:
7206         return -1;
7207     }
7208
7209     if (((*fromX = xSqr) < 0) ||
7210         ((*fromY = ySqr) < 0)) {
7211         *fromX = *fromY = -1;
7212         return -1;
7213     }
7214     if (flipView)
7215       *fromX = BOARD_WIDTH - 1 - *fromX;
7216     else
7217       *fromY = BOARD_HEIGHT - 1 - *fromY;
7218
7219     return whichMenu;
7220 }
7221
7222 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7223 {
7224 //    char * hint = lastHint;
7225     FrontEndProgramStats stats;
7226
7227     stats.which = cps == &first ? 0 : 1;
7228     stats.depth = cpstats->depth;
7229     stats.nodes = cpstats->nodes;
7230     stats.score = cpstats->score;
7231     stats.time = cpstats->time;
7232     stats.pv = cpstats->movelist;
7233     stats.hint = lastHint;
7234     stats.an_move_index = 0;
7235     stats.an_move_count = 0;
7236
7237     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7238         stats.hint = cpstats->move_name;
7239         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7240         stats.an_move_count = cpstats->nr_moves;
7241     }
7242
7243     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
7244
7245     SetProgramStats( &stats );
7246 }
7247
7248 void
7249 ClearEngineOutputPane(int which)
7250 {
7251     static FrontEndProgramStats dummyStats;
7252     dummyStats.which = which;
7253     dummyStats.pv = "#";
7254     SetProgramStats( &dummyStats );
7255 }
7256
7257 #define MAXPLAYERS 500
7258
7259 char *
7260 TourneyStandings(int display)
7261 {
7262     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7263     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7264     char result, *p, *names[MAXPLAYERS];
7265
7266     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7267         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7268     names[0] = p = strdup(appData.participants);
7269     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7270
7271     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7272
7273     while(result = appData.results[nr]) {
7274         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7275         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7276         wScore = bScore = 0;
7277         switch(result) {
7278           case '+': wScore = 2; break;
7279           case '-': bScore = 2; break;
7280           case '=': wScore = bScore = 1; break;
7281           case ' ':
7282           case '*': return strdup("busy"); // tourney not finished
7283         }
7284         score[w] += wScore;
7285         score[b] += bScore;
7286         games[w]++;
7287         games[b]++;
7288         nr++;
7289     }
7290     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7291     for(w=0; w<nPlayers; w++) {
7292         bScore = -1;
7293         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7294         ranking[w] = b; points[w] = bScore; score[b] = -2;
7295     }
7296     p = malloc(nPlayers*34+1);
7297     for(w=0; w<nPlayers && w<display; w++)
7298         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7299     free(names[0]);
7300     return p;
7301 }
7302
7303 void
7304 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7305 {       // count all piece types
7306         int p, f, r;
7307         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7308         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7309         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7310                 p = board[r][f];
7311                 pCnt[p]++;
7312                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7313                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7314                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7315                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7316                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7317                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7318         }
7319 }
7320
7321 int
7322 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7323 {
7324         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7325         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7326
7327         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7328         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7329         if(myPawns == 2 && nMine == 3) // KPP
7330             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7331         if(myPawns == 1 && nMine == 2) // KP
7332             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7333         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7334             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7335         if(myPawns) return FALSE;
7336         if(pCnt[WhiteRook+side])
7337             return pCnt[BlackRook-side] ||
7338                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7339                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7340                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7341         if(pCnt[WhiteCannon+side]) {
7342             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7343             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7344         }
7345         if(pCnt[WhiteKnight+side])
7346             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7347         return FALSE;
7348 }
7349
7350 int
7351 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7352 {
7353         VariantClass v = gameInfo.variant;
7354
7355         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7356         if(v == VariantShatranj) return TRUE; // always winnable through baring
7357         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7358         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7359
7360         if(v == VariantXiangqi) {
7361                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7362
7363                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7364                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7365                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7366                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7367                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7368                 if(stale) // we have at least one last-rank P plus perhaps C
7369                     return majors // KPKX
7370                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7371                 else // KCA*E*
7372                     return pCnt[WhiteFerz+side] // KCAK
7373                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7374                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7375                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7376
7377         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7378                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7379
7380                 if(nMine == 1) return FALSE; // bare King
7381                 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
7382                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7383                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7384                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7385                 if(pCnt[WhiteKnight+side])
7386                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7387                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7388                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7389                 if(nBishops)
7390                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7391                 if(pCnt[WhiteAlfil+side])
7392                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7393                 if(pCnt[WhiteWazir+side])
7394                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7395         }
7396
7397         return TRUE;
7398 }
7399
7400 int
7401 CompareWithRights(Board b1, Board b2)
7402 {
7403     int rights = 0;
7404     if(!CompareBoards(b1, b2)) return FALSE;
7405     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7406     /* compare castling rights */
7407     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7408            rights++; /* King lost rights, while rook still had them */
7409     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7410         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7411            rights++; /* but at least one rook lost them */
7412     }
7413     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7414            rights++;
7415     if( b1[CASTLING][5] != NoRights ) {
7416         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7417            rights++;
7418     }
7419     return rights == 0;
7420 }
7421
7422 int
7423 Adjudicate(ChessProgramState *cps)
7424 {       // [HGM] some adjudications useful with buggy engines
7425         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7426         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7427         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7428         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7429         int k, count = 0; static int bare = 1;
7430         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7431         Boolean canAdjudicate = !appData.icsActive;
7432
7433         // most tests only when we understand the game, i.e. legality-checking on
7434             if( appData.testLegality )
7435             {   /* [HGM] Some more adjudications for obstinate engines */
7436                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7437                 static int moveCount = 6;
7438                 ChessMove result;
7439                 char *reason = NULL;
7440
7441                 /* Count what is on board. */
7442                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7443
7444                 /* Some material-based adjudications that have to be made before stalemate test */
7445                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7446                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7447                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7452                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7453                          return 1;
7454                      }
7455                 }
7456
7457                 /* Bare King in Shatranj (loses) or Losers (wins) */
7458                 if( nrW == 1 || nrB == 1) {
7459                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7460                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7461                      if(canAdjudicate && appData.checkMates) {
7462                          if(engineOpponent)
7463                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7464                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7465                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7466                          return 1;
7467                      }
7468                   } else
7469                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7470                   {    /* bare King */
7471                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7472                         if(canAdjudicate && appData.checkMates) {
7473                             /* but only adjudicate if adjudication enabled */
7474                             if(engineOpponent)
7475                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7476                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7477                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7478                             return 1;
7479                         }
7480                   }
7481                 } else bare = 1;
7482
7483
7484             // don't wait for engine to announce game end if we can judge ourselves
7485             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7486               case MT_CHECK:
7487                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7488                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7489                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7490                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7491                             checkCnt++;
7492                         if(checkCnt >= 2) {
7493                             reason = "Xboard adjudication: 3rd check";
7494                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7495                             break;
7496                         }
7497                     }
7498                 }
7499               case MT_NONE:
7500               default:
7501                 break;
7502               case MT_STALEMATE:
7503               case MT_STAINMATE:
7504                 reason = "Xboard adjudication: Stalemate";
7505                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7506                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7507                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7508                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7509                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7510                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7511                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7512                                                                         EP_CHECKMATE : EP_WINS);
7513                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7514                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7515                 }
7516                 break;
7517               case MT_CHECKMATE:
7518                 reason = "Xboard adjudication: Checkmate";
7519                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7520                 break;
7521             }
7522
7523                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7524                     case EP_STALEMATE:
7525                         result = GameIsDrawn; break;
7526                     case EP_CHECKMATE:
7527                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7528                     case EP_WINS:
7529                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7530                     default:
7531                         result = EndOfFile;
7532                 }
7533                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7534                     if(engineOpponent)
7535                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7536                     GameEnds( result, reason, GE_XBOARD );
7537                     return 1;
7538                 }
7539
7540                 /* Next absolutely insufficient mating material. */
7541                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7542                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7543                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7544
7545                      /* always flag draws, for judging claims */
7546                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7547
7548                      if(canAdjudicate && appData.materialDraws) {
7549                          /* but only adjudicate them if adjudication enabled */
7550                          if(engineOpponent) {
7551                            SendToProgram("force\n", engineOpponent); // suppress reply
7552                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7553                          }
7554                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7555                          return 1;
7556                      }
7557                 }
7558
7559                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7560                 if(gameInfo.variant == VariantXiangqi ?
7561                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7562                  : nrW + nrB == 4 &&
7563                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7564                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7565                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7566                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7567                    ) ) {
7568                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7569                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7570                           if(engineOpponent) {
7571                             SendToProgram("force\n", engineOpponent); // suppress reply
7572                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7573                           }
7574                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7575                           return 1;
7576                      }
7577                 } else moveCount = 6;
7578             }
7579         if (appData.debugMode) { int i;
7580             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7581                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7582                     appData.drawRepeats);
7583             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7584               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7585
7586         }
7587
7588         // Repetition draws and 50-move rule can be applied independently of legality testing
7589
7590                 /* Check for rep-draws */
7591                 count = 0;
7592                 for(k = forwardMostMove-2;
7593                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7594                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7595                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7596                     k-=2)
7597                 {   int rights=0;
7598                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7599                         /* compare castling rights */
7600                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7601                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7602                                 rights++; /* King lost rights, while rook still had them */
7603                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7604                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7605                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7606                                    rights++; /* but at least one rook lost them */
7607                         }
7608                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7609                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7610                                 rights++;
7611                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7612                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7613                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7614                                    rights++;
7615                         }
7616                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7617                             && appData.drawRepeats > 1) {
7618                              /* adjudicate after user-specified nr of repeats */
7619                              int result = GameIsDrawn;
7620                              char *details = "XBoard adjudication: repetition draw";
7621                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7622                                 // [HGM] xiangqi: check for forbidden perpetuals
7623                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7624                                 for(m=forwardMostMove; m>k; m-=2) {
7625                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7626                                         ourPerpetual = 0; // the current mover did not always check
7627                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7628                                         hisPerpetual = 0; // the opponent did not always check
7629                                 }
7630                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7631                                                                         ourPerpetual, hisPerpetual);
7632                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7633                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7634                                     details = "Xboard adjudication: perpetual checking";
7635                                 } else
7636                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7637                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7638                                 } else
7639                                 // Now check for perpetual chases
7640                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7641                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7642                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7643                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7644                                         static char resdet[MSG_SIZ];
7645                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7646                                         details = resdet;
7647                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7648                                     } else
7649                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7650                                         break; // Abort repetition-checking loop.
7651                                 }
7652                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7653                              }
7654                              if(engineOpponent) {
7655                                SendToProgram("force\n", engineOpponent); // suppress reply
7656                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7657                              }
7658                              GameEnds( result, details, GE_XBOARD );
7659                              return 1;
7660                         }
7661                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7662                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7663                     }
7664                 }
7665
7666                 /* Now we test for 50-move draws. Determine ply count */
7667                 count = forwardMostMove;
7668                 /* look for last irreversble move */
7669                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7670                     count--;
7671                 /* if we hit starting position, add initial plies */
7672                 if( count == backwardMostMove )
7673                     count -= initialRulePlies;
7674                 count = forwardMostMove - count;
7675                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7676                         // adjust reversible move counter for checks in Xiangqi
7677                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7678                         if(i < backwardMostMove) i = backwardMostMove;
7679                         while(i <= forwardMostMove) {
7680                                 lastCheck = inCheck; // check evasion does not count
7681                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7682                                 if(inCheck || lastCheck) count--; // check does not count
7683                                 i++;
7684                         }
7685                 }
7686                 if( count >= 100)
7687                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7688                          /* this is used to judge if draw claims are legal */
7689                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7690                          if(engineOpponent) {
7691                            SendToProgram("force\n", engineOpponent); // suppress reply
7692                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7693                          }
7694                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7695                          return 1;
7696                 }
7697
7698                 /* if draw offer is pending, treat it as a draw claim
7699                  * when draw condition present, to allow engines a way to
7700                  * claim draws before making their move to avoid a race
7701                  * condition occurring after their move
7702                  */
7703                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7704                          char *p = NULL;
7705                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7706                              p = "Draw claim: 50-move rule";
7707                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7708                              p = "Draw claim: 3-fold repetition";
7709                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7710                              p = "Draw claim: insufficient mating material";
7711                          if( p != NULL && canAdjudicate) {
7712                              if(engineOpponent) {
7713                                SendToProgram("force\n", engineOpponent); // suppress reply
7714                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7715                              }
7716                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7717                              return 1;
7718                          }
7719                 }
7720
7721                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7722                     if(engineOpponent) {
7723                       SendToProgram("force\n", engineOpponent); // suppress reply
7724                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7725                     }
7726                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7727                     return 1;
7728                 }
7729         return 0;
7730 }
7731
7732 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7733 {   // [HGM] book: this routine intercepts moves to simulate book replies
7734     char *bookHit = NULL;
7735
7736     //first determine if the incoming move brings opponent into his book
7737     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7738         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7739     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7740     if(bookHit != NULL && !cps->bookSuspend) {
7741         // make sure opponent is not going to reply after receiving move to book position
7742         SendToProgram("force\n", cps);
7743         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7744     }
7745     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7746     // now arrange restart after book miss
7747     if(bookHit) {
7748         // after a book hit we never send 'go', and the code after the call to this routine
7749         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7750         char buf[MSG_SIZ], *move = bookHit;
7751         if(cps->useSAN) {
7752             int fromX, fromY, toX, toY;
7753             char promoChar;
7754             ChessMove moveType;
7755             move = buf + 30;
7756             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7757                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7758                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7759                                     PosFlags(forwardMostMove),
7760                                     fromY, fromX, toY, toX, promoChar, move);
7761             } else {
7762                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7763                 bookHit = NULL;
7764             }
7765         }
7766         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7767         SendToProgram(buf, cps);
7768         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7769     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7770         SendToProgram("go\n", cps);
7771         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7772     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7773         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7774             SendToProgram("go\n", cps);
7775         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7776     }
7777     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7778 }
7779
7780 char *savedMessage;
7781 ChessProgramState *savedState;
7782 void DeferredBookMove(void)
7783 {
7784         if(savedState->lastPing != savedState->lastPong)
7785                     ScheduleDelayedEvent(DeferredBookMove, 10);
7786         else
7787         HandleMachineMove(savedMessage, savedState);
7788 }
7789
7790 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7791
7792 void
7793 HandleMachineMove(message, cps)
7794      char *message;
7795      ChessProgramState *cps;
7796 {
7797     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7798     char realname[MSG_SIZ];
7799     int fromX, fromY, toX, toY;
7800     ChessMove moveType;
7801     char promoChar;
7802     char *p, *pv=buf1;
7803     int machineWhite;
7804     char *bookHit;
7805
7806     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7807         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7808         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7809             DisplayError(_("Invalid pairing from pairing engine"), 0);
7810             return;
7811         }
7812         pairingReceived = 1;
7813         NextMatchGame();
7814         return; // Skim the pairing messages here.
7815     }
7816
7817     cps->userError = 0;
7818
7819 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7820     /*
7821      * Kludge to ignore BEL characters
7822      */
7823     while (*message == '\007') message++;
7824
7825     /*
7826      * [HGM] engine debug message: ignore lines starting with '#' character
7827      */
7828     if(cps->debug && *message == '#') return;
7829
7830     /*
7831      * Look for book output
7832      */
7833     if (cps == &first && bookRequested) {
7834         if (message[0] == '\t' || message[0] == ' ') {
7835             /* Part of the book output is here; append it */
7836             strcat(bookOutput, message);
7837             strcat(bookOutput, "  \n");
7838             return;
7839         } else if (bookOutput[0] != NULLCHAR) {
7840             /* All of book output has arrived; display it */
7841             char *p = bookOutput;
7842             while (*p != NULLCHAR) {
7843                 if (*p == '\t') *p = ' ';
7844                 p++;
7845             }
7846             DisplayInformation(bookOutput);
7847             bookRequested = FALSE;
7848             /* Fall through to parse the current output */
7849         }
7850     }
7851
7852     /*
7853      * Look for machine move.
7854      */
7855     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7856         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7857     {
7858         /* This method is only useful on engines that support ping */
7859         if (cps->lastPing != cps->lastPong) {
7860           if (gameMode == BeginningOfGame) {
7861             /* Extra move from before last new; ignore */
7862             if (appData.debugMode) {
7863                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7864             }
7865           } else {
7866             if (appData.debugMode) {
7867                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7868                         cps->which, gameMode);
7869             }
7870
7871             SendToProgram("undo\n", cps);
7872           }
7873           return;
7874         }
7875
7876         switch (gameMode) {
7877           case BeginningOfGame:
7878             /* Extra move from before last reset; ignore */
7879             if (appData.debugMode) {
7880                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7881             }
7882             return;
7883
7884           case EndOfGame:
7885           case IcsIdle:
7886           default:
7887             /* Extra move after we tried to stop.  The mode test is
7888                not a reliable way of detecting this problem, but it's
7889                the best we can do on engines that don't support ping.
7890             */
7891             if (appData.debugMode) {
7892                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7893                         cps->which, gameMode);
7894             }
7895             SendToProgram("undo\n", cps);
7896             return;
7897
7898           case MachinePlaysWhite:
7899           case IcsPlayingWhite:
7900             machineWhite = TRUE;
7901             break;
7902
7903           case MachinePlaysBlack:
7904           case IcsPlayingBlack:
7905             machineWhite = FALSE;
7906             break;
7907
7908           case TwoMachinesPlay:
7909             machineWhite = (cps->twoMachinesColor[0] == 'w');
7910             break;
7911         }
7912         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7913             if (appData.debugMode) {
7914                 fprintf(debugFP,
7915                         "Ignoring move out of turn by %s, gameMode %d"
7916                         ", forwardMost %d\n",
7917                         cps->which, gameMode, forwardMostMove);
7918             }
7919             return;
7920         }
7921
7922     if (appData.debugMode) { int f = forwardMostMove;
7923         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7924                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7925                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7926     }
7927         if(cps->alphaRank) AlphaRank(machineMove, 4);
7928         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7929                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7930             /* Machine move could not be parsed; ignore it. */
7931           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7932                     machineMove, _(cps->which));
7933             DisplayError(buf1, 0);
7934             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7935                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7936             if (gameMode == TwoMachinesPlay) {
7937               GameEnds(machineWhite ? BlackWins : WhiteWins,
7938                        buf1, GE_XBOARD);
7939             }
7940             return;
7941         }
7942
7943         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7944         /* So we have to redo legality test with true e.p. status here,  */
7945         /* to make sure an illegal e.p. capture does not slip through,   */
7946         /* to cause a forfeit on a justified illegal-move complaint      */
7947         /* of the opponent.                                              */
7948         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7949            ChessMove moveType;
7950            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7951                              fromY, fromX, toY, toX, promoChar);
7952             if (appData.debugMode) {
7953                 int i;
7954                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7955                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7956                 fprintf(debugFP, "castling rights\n");
7957             }
7958             if(moveType == IllegalMove) {
7959               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7960                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7961                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7962                            buf1, GE_XBOARD);
7963                 return;
7964            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7965            /* [HGM] Kludge to handle engines that send FRC-style castling
7966               when they shouldn't (like TSCP-Gothic) */
7967            switch(moveType) {
7968              case WhiteASideCastleFR:
7969              case BlackASideCastleFR:
7970                toX+=2;
7971                currentMoveString[2]++;
7972                break;
7973              case WhiteHSideCastleFR:
7974              case BlackHSideCastleFR:
7975                toX--;
7976                currentMoveString[2]--;
7977                break;
7978              default: ; // nothing to do, but suppresses warning of pedantic compilers
7979            }
7980         }
7981         hintRequested = FALSE;
7982         lastHint[0] = NULLCHAR;
7983         bookRequested = FALSE;
7984         /* Program may be pondering now */
7985         cps->maybeThinking = TRUE;
7986         if (cps->sendTime == 2) cps->sendTime = 1;
7987         if (cps->offeredDraw) cps->offeredDraw--;
7988
7989         /* [AS] Save move info*/
7990         pvInfoList[ forwardMostMove ].score = programStats.score;
7991         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7992         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7993
7994         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7995
7996         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7997         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7998             int count = 0;
7999
8000             while( count < adjudicateLossPlies ) {
8001                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8002
8003                 if( count & 1 ) {
8004                     score = -score; /* Flip score for winning side */
8005                 }
8006
8007                 if( score > adjudicateLossThreshold ) {
8008                     break;
8009                 }
8010
8011                 count++;
8012             }
8013
8014             if( count >= adjudicateLossPlies ) {
8015                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8016
8017                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8018                     "Xboard adjudication",
8019                     GE_XBOARD );
8020
8021                 return;
8022             }
8023         }
8024
8025         if(Adjudicate(cps)) {
8026             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8027             return; // [HGM] adjudicate: for all automatic game ends
8028         }
8029
8030 #if ZIPPY
8031         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8032             first.initDone) {
8033           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8034                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8035                 SendToICS("draw ");
8036                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8037           }
8038           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8039           ics_user_moved = 1;
8040           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8041                 char buf[3*MSG_SIZ];
8042
8043                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8044                         programStats.score / 100.,
8045                         programStats.depth,
8046                         programStats.time / 100.,
8047                         (unsigned int)programStats.nodes,
8048                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8049                         programStats.movelist);
8050                 SendToICS(buf);
8051 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8052           }
8053         }
8054 #endif
8055
8056         /* [AS] Clear stats for next move */
8057         ClearProgramStats();
8058         thinkOutput[0] = NULLCHAR;
8059         hiddenThinkOutputState = 0;
8060
8061         bookHit = NULL;
8062         if (gameMode == TwoMachinesPlay) {
8063             /* [HGM] relaying draw offers moved to after reception of move */
8064             /* and interpreting offer as claim if it brings draw condition */
8065             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8066                 SendToProgram("draw\n", cps->other);
8067             }
8068             if (cps->other->sendTime) {
8069                 SendTimeRemaining(cps->other,
8070                                   cps->other->twoMachinesColor[0] == 'w');
8071             }
8072             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8073             if (firstMove && !bookHit) {
8074                 firstMove = FALSE;
8075                 if (cps->other->useColors) {
8076                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8077                 }
8078                 SendToProgram("go\n", cps->other);
8079             }
8080             cps->other->maybeThinking = TRUE;
8081         }
8082
8083         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8084
8085         if (!pausing && appData.ringBellAfterMoves) {
8086             RingBell();
8087         }
8088
8089         /*
8090          * Reenable menu items that were disabled while
8091          * machine was thinking
8092          */
8093         if (gameMode != TwoMachinesPlay)
8094             SetUserThinkingEnables();
8095
8096         // [HGM] book: after book hit opponent has received move and is now in force mode
8097         // force the book reply into it, and then fake that it outputted this move by jumping
8098         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8099         if(bookHit) {
8100                 static char bookMove[MSG_SIZ]; // a bit generous?
8101
8102                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8103                 strcat(bookMove, bookHit);
8104                 message = bookMove;
8105                 cps = cps->other;
8106                 programStats.nodes = programStats.depth = programStats.time =
8107                 programStats.score = programStats.got_only_move = 0;
8108                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8109
8110                 if(cps->lastPing != cps->lastPong) {
8111                     savedMessage = message; // args for deferred call
8112                     savedState = cps;
8113                     ScheduleDelayedEvent(DeferredBookMove, 10);
8114                     return;
8115                 }
8116                 goto FakeBookMove;
8117         }
8118
8119         return;
8120     }
8121
8122     /* Set special modes for chess engines.  Later something general
8123      *  could be added here; for now there is just one kludge feature,
8124      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8125      *  when "xboard" is given as an interactive command.
8126      */
8127     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8128         cps->useSigint = FALSE;
8129         cps->useSigterm = FALSE;
8130     }
8131     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8132       ParseFeatures(message+8, cps);
8133       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8134     }
8135
8136     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8137                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8138       int dummy, s=6; char buf[MSG_SIZ];
8139       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8140       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8141       if(startedFromSetupPosition) return;
8142       ParseFEN(boards[0], &dummy, message+s);
8143       DrawPosition(TRUE, boards[0]);
8144       startedFromSetupPosition = TRUE;
8145       return;
8146     }
8147     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8148      * want this, I was asked to put it in, and obliged.
8149      */
8150     if (!strncmp(message, "setboard ", 9)) {
8151         Board initial_position;
8152
8153         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8154
8155         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8156             DisplayError(_("Bad FEN received from engine"), 0);
8157             return ;
8158         } else {
8159            Reset(TRUE, FALSE);
8160            CopyBoard(boards[0], initial_position);
8161            initialRulePlies = FENrulePlies;
8162            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8163            else gameMode = MachinePlaysBlack;
8164            DrawPosition(FALSE, boards[currentMove]);
8165         }
8166         return;
8167     }
8168
8169     /*
8170      * Look for communication commands
8171      */
8172     if (!strncmp(message, "telluser ", 9)) {
8173         if(message[9] == '\\' && message[10] == '\\')
8174             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8175         PlayTellSound();
8176         DisplayNote(message + 9);
8177         return;
8178     }
8179     if (!strncmp(message, "tellusererror ", 14)) {
8180         cps->userError = 1;
8181         if(message[14] == '\\' && message[15] == '\\')
8182             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8183         PlayTellSound();
8184         DisplayError(message + 14, 0);
8185         return;
8186     }
8187     if (!strncmp(message, "tellopponent ", 13)) {
8188       if (appData.icsActive) {
8189         if (loggedOn) {
8190           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8191           SendToICS(buf1);
8192         }
8193       } else {
8194         DisplayNote(message + 13);
8195       }
8196       return;
8197     }
8198     if (!strncmp(message, "tellothers ", 11)) {
8199       if (appData.icsActive) {
8200         if (loggedOn) {
8201           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8202           SendToICS(buf1);
8203         }
8204       }
8205       return;
8206     }
8207     if (!strncmp(message, "tellall ", 8)) {
8208       if (appData.icsActive) {
8209         if (loggedOn) {
8210           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8211           SendToICS(buf1);
8212         }
8213       } else {
8214         DisplayNote(message + 8);
8215       }
8216       return;
8217     }
8218     if (strncmp(message, "warning", 7) == 0) {
8219         /* Undocumented feature, use tellusererror in new code */
8220         DisplayError(message, 0);
8221         return;
8222     }
8223     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8224         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8225         strcat(realname, " query");
8226         AskQuestion(realname, buf2, buf1, cps->pr);
8227         return;
8228     }
8229     /* Commands from the engine directly to ICS.  We don't allow these to be
8230      *  sent until we are logged on. Crafty kibitzes have been known to
8231      *  interfere with the login process.
8232      */
8233     if (loggedOn) {
8234         if (!strncmp(message, "tellics ", 8)) {
8235             SendToICS(message + 8);
8236             SendToICS("\n");
8237             return;
8238         }
8239         if (!strncmp(message, "tellicsnoalias ", 15)) {
8240             SendToICS(ics_prefix);
8241             SendToICS(message + 15);
8242             SendToICS("\n");
8243             return;
8244         }
8245         /* The following are for backward compatibility only */
8246         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8247             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8248             SendToICS(ics_prefix);
8249             SendToICS(message);
8250             SendToICS("\n");
8251             return;
8252         }
8253     }
8254     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8255         return;
8256     }
8257     /*
8258      * If the move is illegal, cancel it and redraw the board.
8259      * Also deal with other error cases.  Matching is rather loose
8260      * here to accommodate engines written before the spec.
8261      */
8262     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8263         strncmp(message, "Error", 5) == 0) {
8264         if (StrStr(message, "name") ||
8265             StrStr(message, "rating") || StrStr(message, "?") ||
8266             StrStr(message, "result") || StrStr(message, "board") ||
8267             StrStr(message, "bk") || StrStr(message, "computer") ||
8268             StrStr(message, "variant") || StrStr(message, "hint") ||
8269             StrStr(message, "random") || StrStr(message, "depth") ||
8270             StrStr(message, "accepted")) {
8271             return;
8272         }
8273         if (StrStr(message, "protover")) {
8274           /* Program is responding to input, so it's apparently done
8275              initializing, and this error message indicates it is
8276              protocol version 1.  So we don't need to wait any longer
8277              for it to initialize and send feature commands. */
8278           FeatureDone(cps, 1);
8279           cps->protocolVersion = 1;
8280           return;
8281         }
8282         cps->maybeThinking = FALSE;
8283
8284         if (StrStr(message, "draw")) {
8285             /* Program doesn't have "draw" command */
8286             cps->sendDrawOffers = 0;
8287             return;
8288         }
8289         if (cps->sendTime != 1 &&
8290             (StrStr(message, "time") || StrStr(message, "otim"))) {
8291           /* Program apparently doesn't have "time" or "otim" command */
8292           cps->sendTime = 0;
8293           return;
8294         }
8295         if (StrStr(message, "analyze")) {
8296             cps->analysisSupport = FALSE;
8297             cps->analyzing = FALSE;
8298 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8299             EditGameEvent(); // [HGM] try to preserve loaded game
8300             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8301             DisplayError(buf2, 0);
8302             return;
8303         }
8304         if (StrStr(message, "(no matching move)st")) {
8305           /* Special kludge for GNU Chess 4 only */
8306           cps->stKludge = TRUE;
8307           SendTimeControl(cps, movesPerSession, timeControl,
8308                           timeIncrement, appData.searchDepth,
8309                           searchTime);
8310           return;
8311         }
8312         if (StrStr(message, "(no matching move)sd")) {
8313           /* Special kludge for GNU Chess 4 only */
8314           cps->sdKludge = TRUE;
8315           SendTimeControl(cps, movesPerSession, timeControl,
8316                           timeIncrement, appData.searchDepth,
8317                           searchTime);
8318           return;
8319         }
8320         if (!StrStr(message, "llegal")) {
8321             return;
8322         }
8323         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8324             gameMode == IcsIdle) return;
8325         if (forwardMostMove <= backwardMostMove) return;
8326         if (pausing) PauseEvent();
8327       if(appData.forceIllegal) {
8328             // [HGM] illegal: machine refused move; force position after move into it
8329           SendToProgram("force\n", cps);
8330           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8331                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8332                 // when black is to move, while there might be nothing on a2 or black
8333                 // might already have the move. So send the board as if white has the move.
8334                 // But first we must change the stm of the engine, as it refused the last move
8335                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8336                 if(WhiteOnMove(forwardMostMove)) {
8337                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8338                     SendBoard(cps, forwardMostMove); // kludgeless board
8339                 } else {
8340                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8341                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8342                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8343                 }
8344           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8345             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8346                  gameMode == TwoMachinesPlay)
8347               SendToProgram("go\n", cps);
8348             return;
8349       } else
8350         if (gameMode == PlayFromGameFile) {
8351             /* Stop reading this game file */
8352             gameMode = EditGame;
8353             ModeHighlight();
8354         }
8355         /* [HGM] illegal-move claim should forfeit game when Xboard */
8356         /* only passes fully legal moves                            */
8357         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8358             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8359                                 "False illegal-move claim", GE_XBOARD );
8360             return; // do not take back move we tested as valid
8361         }
8362         currentMove = forwardMostMove-1;
8363         DisplayMove(currentMove-1); /* before DisplayMoveError */
8364         SwitchClocks(forwardMostMove-1); // [HGM] race
8365         DisplayBothClocks();
8366         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8367                 parseList[currentMove], _(cps->which));
8368         DisplayMoveError(buf1);
8369         DrawPosition(FALSE, boards[currentMove]);
8370
8371         SetUserThinkingEnables();
8372         return;
8373     }
8374     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8375         /* Program has a broken "time" command that
8376            outputs a string not ending in newline.
8377            Don't use it. */
8378         cps->sendTime = 0;
8379     }
8380
8381     /*
8382      * If chess program startup fails, exit with an error message.
8383      * Attempts to recover here are futile.
8384      */
8385     if ((StrStr(message, "unknown host") != NULL)
8386         || (StrStr(message, "No remote directory") != NULL)
8387         || (StrStr(message, "not found") != NULL)
8388         || (StrStr(message, "No such file") != NULL)
8389         || (StrStr(message, "can't alloc") != NULL)
8390         || (StrStr(message, "Permission denied") != NULL)) {
8391
8392         cps->maybeThinking = FALSE;
8393         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8394                 _(cps->which), cps->program, cps->host, message);
8395         RemoveInputSource(cps->isr);
8396         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8397             if(cps == &first) appData.noChessProgram = TRUE;
8398             DisplayError(buf1, 0);
8399         }
8400         return;
8401     }
8402
8403     /*
8404      * Look for hint output
8405      */
8406     if (sscanf(message, "Hint: %s", buf1) == 1) {
8407         if (cps == &first && hintRequested) {
8408             hintRequested = FALSE;
8409             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8410                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8411                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8412                                     PosFlags(forwardMostMove),
8413                                     fromY, fromX, toY, toX, promoChar, buf1);
8414                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8415                 DisplayInformation(buf2);
8416             } else {
8417                 /* Hint move could not be parsed!? */
8418               snprintf(buf2, sizeof(buf2),
8419                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8420                         buf1, _(cps->which));
8421                 DisplayError(buf2, 0);
8422             }
8423         } else {
8424           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8425         }
8426         return;
8427     }
8428
8429     /*
8430      * Ignore other messages if game is not in progress
8431      */
8432     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8433         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8434
8435     /*
8436      * look for win, lose, draw, or draw offer
8437      */
8438     if (strncmp(message, "1-0", 3) == 0) {
8439         char *p, *q, *r = "";
8440         p = strchr(message, '{');
8441         if (p) {
8442             q = strchr(p, '}');
8443             if (q) {
8444                 *q = NULLCHAR;
8445                 r = p + 1;
8446             }
8447         }
8448         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8449         return;
8450     } else if (strncmp(message, "0-1", 3) == 0) {
8451         char *p, *q, *r = "";
8452         p = strchr(message, '{');
8453         if (p) {
8454             q = strchr(p, '}');
8455             if (q) {
8456                 *q = NULLCHAR;
8457                 r = p + 1;
8458             }
8459         }
8460         /* Kludge for Arasan 4.1 bug */
8461         if (strcmp(r, "Black resigns") == 0) {
8462             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8463             return;
8464         }
8465         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strncmp(message, "1/2", 3) == 0) {
8468         char *p, *q, *r = "";
8469         p = strchr(message, '{');
8470         if (p) {
8471             q = strchr(p, '}');
8472             if (q) {
8473                 *q = NULLCHAR;
8474                 r = p + 1;
8475             }
8476         }
8477
8478         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8479         return;
8480
8481     } else if (strncmp(message, "White resign", 12) == 0) {
8482         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8483         return;
8484     } else if (strncmp(message, "Black resign", 12) == 0) {
8485         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8486         return;
8487     } else if (strncmp(message, "White matches", 13) == 0 ||
8488                strncmp(message, "Black matches", 13) == 0   ) {
8489         /* [HGM] ignore GNUShogi noises */
8490         return;
8491     } else if (strncmp(message, "White", 5) == 0 &&
8492                message[5] != '(' &&
8493                StrStr(message, "Black") == NULL) {
8494         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8495         return;
8496     } else if (strncmp(message, "Black", 5) == 0 &&
8497                message[5] != '(') {
8498         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8499         return;
8500     } else if (strcmp(message, "resign") == 0 ||
8501                strcmp(message, "computer resigns") == 0) {
8502         switch (gameMode) {
8503           case MachinePlaysBlack:
8504           case IcsPlayingBlack:
8505             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8506             break;
8507           case MachinePlaysWhite:
8508           case IcsPlayingWhite:
8509             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8510             break;
8511           case TwoMachinesPlay:
8512             if (cps->twoMachinesColor[0] == 'w')
8513               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8514             else
8515               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8516             break;
8517           default:
8518             /* can't happen */
8519             break;
8520         }
8521         return;
8522     } else if (strncmp(message, "opponent mates", 14) == 0) {
8523         switch (gameMode) {
8524           case MachinePlaysBlack:
8525           case IcsPlayingBlack:
8526             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8527             break;
8528           case MachinePlaysWhite:
8529           case IcsPlayingWhite:
8530             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8531             break;
8532           case TwoMachinesPlay:
8533             if (cps->twoMachinesColor[0] == 'w')
8534               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8535             else
8536               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8537             break;
8538           default:
8539             /* can't happen */
8540             break;
8541         }
8542         return;
8543     } else if (strncmp(message, "computer mates", 14) == 0) {
8544         switch (gameMode) {
8545           case MachinePlaysBlack:
8546           case IcsPlayingBlack:
8547             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8548             break;
8549           case MachinePlaysWhite:
8550           case IcsPlayingWhite:
8551             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8552             break;
8553           case TwoMachinesPlay:
8554             if (cps->twoMachinesColor[0] == 'w')
8555               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8556             else
8557               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8558             break;
8559           default:
8560             /* can't happen */
8561             break;
8562         }
8563         return;
8564     } else if (strncmp(message, "checkmate", 9) == 0) {
8565         if (WhiteOnMove(forwardMostMove)) {
8566             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8567         } else {
8568             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8569         }
8570         return;
8571     } else if (strstr(message, "Draw") != NULL ||
8572                strstr(message, "game is a draw") != NULL) {
8573         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8574         return;
8575     } else if (strstr(message, "offer") != NULL &&
8576                strstr(message, "draw") != NULL) {
8577 #if ZIPPY
8578         if (appData.zippyPlay && first.initDone) {
8579             /* Relay offer to ICS */
8580             SendToICS(ics_prefix);
8581             SendToICS("draw\n");
8582         }
8583 #endif
8584         cps->offeredDraw = 2; /* valid until this engine moves twice */
8585         if (gameMode == TwoMachinesPlay) {
8586             if (cps->other->offeredDraw) {
8587                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8588             /* [HGM] in two-machine mode we delay relaying draw offer      */
8589             /* until after we also have move, to see if it is really claim */
8590             }
8591         } else if (gameMode == MachinePlaysWhite ||
8592                    gameMode == MachinePlaysBlack) {
8593           if (userOfferedDraw) {
8594             DisplayInformation(_("Machine accepts your draw offer"));
8595             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8596           } else {
8597             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8598           }
8599         }
8600     }
8601
8602
8603     /*
8604      * Look for thinking output
8605      */
8606     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8607           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8608                                 ) {
8609         int plylev, mvleft, mvtot, curscore, time;
8610         char mvname[MOVE_LEN];
8611         u64 nodes; // [DM]
8612         char plyext;
8613         int ignore = FALSE;
8614         int prefixHint = FALSE;
8615         mvname[0] = NULLCHAR;
8616
8617         switch (gameMode) {
8618           case MachinePlaysBlack:
8619           case IcsPlayingBlack:
8620             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8621             break;
8622           case MachinePlaysWhite:
8623           case IcsPlayingWhite:
8624             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8625             break;
8626           case AnalyzeMode:
8627           case AnalyzeFile:
8628             break;
8629           case IcsObserving: /* [DM] icsEngineAnalyze */
8630             if (!appData.icsEngineAnalyze) ignore = TRUE;
8631             break;
8632           case TwoMachinesPlay:
8633             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8634                 ignore = TRUE;
8635             }
8636             break;
8637           default:
8638             ignore = TRUE;
8639             break;
8640         }
8641
8642         if (!ignore) {
8643             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8644             buf1[0] = NULLCHAR;
8645             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8646                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8647
8648                 if (plyext != ' ' && plyext != '\t') {
8649                     time *= 100;
8650                 }
8651
8652                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8653                 if( cps->scoreIsAbsolute &&
8654                     ( gameMode == MachinePlaysBlack ||
8655                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8656                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8657                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8658                      !WhiteOnMove(currentMove)
8659                     ) )
8660                 {
8661                     curscore = -curscore;
8662                 }
8663
8664                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8665
8666                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8667                         char buf[MSG_SIZ];
8668                         FILE *f;
8669                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8670                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8671                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8672                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8673                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8674                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8675                                 fclose(f);
8676                         } else DisplayError(_("failed writing PV"), 0);
8677                 }
8678
8679                 tempStats.depth = plylev;
8680                 tempStats.nodes = nodes;
8681                 tempStats.time = time;
8682                 tempStats.score = curscore;
8683                 tempStats.got_only_move = 0;
8684
8685                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8686                         int ticklen;
8687
8688                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8689                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8690                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8691                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8692                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8693                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8694                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8695                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8696                 }
8697
8698                 /* Buffer overflow protection */
8699                 if (pv[0] != NULLCHAR) {
8700                     if (strlen(pv) >= sizeof(tempStats.movelist)
8701                         && appData.debugMode) {
8702                         fprintf(debugFP,
8703                                 "PV is too long; using the first %u bytes.\n",
8704                                 (unsigned) sizeof(tempStats.movelist) - 1);
8705                     }
8706
8707                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8708                 } else {
8709                     sprintf(tempStats.movelist, " no PV\n");
8710                 }
8711
8712                 if (tempStats.seen_stat) {
8713                     tempStats.ok_to_send = 1;
8714                 }
8715
8716                 if (strchr(tempStats.movelist, '(') != NULL) {
8717                     tempStats.line_is_book = 1;
8718                     tempStats.nr_moves = 0;
8719                     tempStats.moves_left = 0;
8720                 } else {
8721                     tempStats.line_is_book = 0;
8722                 }
8723
8724                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8725                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8726
8727                 SendProgramStatsToFrontend( cps, &tempStats );
8728
8729                 /*
8730                     [AS] Protect the thinkOutput buffer from overflow... this
8731                     is only useful if buf1 hasn't overflowed first!
8732                 */
8733                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8734                          plylev,
8735                          (gameMode == TwoMachinesPlay ?
8736                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8737                          ((double) curscore) / 100.0,
8738                          prefixHint ? lastHint : "",
8739                          prefixHint ? " " : "" );
8740
8741                 if( buf1[0] != NULLCHAR ) {
8742                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8743
8744                     if( strlen(pv) > max_len ) {
8745                         if( appData.debugMode) {
8746                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8747                         }
8748                         pv[max_len+1] = '\0';
8749                     }
8750
8751                     strcat( thinkOutput, pv);
8752                 }
8753
8754                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8755                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8756                     DisplayMove(currentMove - 1);
8757                 }
8758                 return;
8759
8760             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8761                 /* crafty (9.25+) says "(only move) <move>"
8762                  * if there is only 1 legal move
8763                  */
8764                 sscanf(p, "(only move) %s", buf1);
8765                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8766                 sprintf(programStats.movelist, "%s (only move)", buf1);
8767                 programStats.depth = 1;
8768                 programStats.nr_moves = 1;
8769                 programStats.moves_left = 1;
8770                 programStats.nodes = 1;
8771                 programStats.time = 1;
8772                 programStats.got_only_move = 1;
8773
8774                 /* Not really, but we also use this member to
8775                    mean "line isn't going to change" (Crafty
8776                    isn't searching, so stats won't change) */
8777                 programStats.line_is_book = 1;
8778
8779                 SendProgramStatsToFrontend( cps, &programStats );
8780
8781                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8782                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8783                     DisplayMove(currentMove - 1);
8784                 }
8785                 return;
8786             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8787                               &time, &nodes, &plylev, &mvleft,
8788                               &mvtot, mvname) >= 5) {
8789                 /* The stat01: line is from Crafty (9.29+) in response
8790                    to the "." command */
8791                 programStats.seen_stat = 1;
8792                 cps->maybeThinking = TRUE;
8793
8794                 if (programStats.got_only_move || !appData.periodicUpdates)
8795                   return;
8796
8797                 programStats.depth = plylev;
8798                 programStats.time = time;
8799                 programStats.nodes = nodes;
8800                 programStats.moves_left = mvleft;
8801                 programStats.nr_moves = mvtot;
8802                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8803                 programStats.ok_to_send = 1;
8804                 programStats.movelist[0] = '\0';
8805
8806                 SendProgramStatsToFrontend( cps, &programStats );
8807
8808                 return;
8809
8810             } else if (strncmp(message,"++",2) == 0) {
8811                 /* Crafty 9.29+ outputs this */
8812                 programStats.got_fail = 2;
8813                 return;
8814
8815             } else if (strncmp(message,"--",2) == 0) {
8816                 /* Crafty 9.29+ outputs this */
8817                 programStats.got_fail = 1;
8818                 return;
8819
8820             } else if (thinkOutput[0] != NULLCHAR &&
8821                        strncmp(message, "    ", 4) == 0) {
8822                 unsigned message_len;
8823
8824                 p = message;
8825                 while (*p && *p == ' ') p++;
8826
8827                 message_len = strlen( p );
8828
8829                 /* [AS] Avoid buffer overflow */
8830                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8831                     strcat(thinkOutput, " ");
8832                     strcat(thinkOutput, p);
8833                 }
8834
8835                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8836                     strcat(programStats.movelist, " ");
8837                     strcat(programStats.movelist, p);
8838                 }
8839
8840                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8841                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8842                     DisplayMove(currentMove - 1);
8843                 }
8844                 return;
8845             }
8846         }
8847         else {
8848             buf1[0] = NULLCHAR;
8849
8850             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8851                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8852             {
8853                 ChessProgramStats cpstats;
8854
8855                 if (plyext != ' ' && plyext != '\t') {
8856                     time *= 100;
8857                 }
8858
8859                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8860                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8861                     curscore = -curscore;
8862                 }
8863
8864                 cpstats.depth = plylev;
8865                 cpstats.nodes = nodes;
8866                 cpstats.time = time;
8867                 cpstats.score = curscore;
8868                 cpstats.got_only_move = 0;
8869                 cpstats.movelist[0] = '\0';
8870
8871                 if (buf1[0] != NULLCHAR) {
8872                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8873                 }
8874
8875                 cpstats.ok_to_send = 0;
8876                 cpstats.line_is_book = 0;
8877                 cpstats.nr_moves = 0;
8878                 cpstats.moves_left = 0;
8879
8880                 SendProgramStatsToFrontend( cps, &cpstats );
8881             }
8882         }
8883     }
8884 }
8885
8886
8887 /* Parse a game score from the character string "game", and
8888    record it as the history of the current game.  The game
8889    score is NOT assumed to start from the standard position.
8890    The display is not updated in any way.
8891    */
8892 void
8893 ParseGameHistory(game)
8894      char *game;
8895 {
8896     ChessMove moveType;
8897     int fromX, fromY, toX, toY, boardIndex;
8898     char promoChar;
8899     char *p, *q;
8900     char buf[MSG_SIZ];
8901
8902     if (appData.debugMode)
8903       fprintf(debugFP, "Parsing game history: %s\n", game);
8904
8905     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8906     gameInfo.site = StrSave(appData.icsHost);
8907     gameInfo.date = PGNDate();
8908     gameInfo.round = StrSave("-");
8909
8910     /* Parse out names of players */
8911     while (*game == ' ') game++;
8912     p = buf;
8913     while (*game != ' ') *p++ = *game++;
8914     *p = NULLCHAR;
8915     gameInfo.white = StrSave(buf);
8916     while (*game == ' ') game++;
8917     p = buf;
8918     while (*game != ' ' && *game != '\n') *p++ = *game++;
8919     *p = NULLCHAR;
8920     gameInfo.black = StrSave(buf);
8921
8922     /* Parse moves */
8923     boardIndex = blackPlaysFirst ? 1 : 0;
8924     yynewstr(game);
8925     for (;;) {
8926         yyboardindex = boardIndex;
8927         moveType = (ChessMove) Myylex();
8928         switch (moveType) {
8929           case IllegalMove:             /* maybe suicide chess, etc. */
8930   if (appData.debugMode) {
8931     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8932     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8933     setbuf(debugFP, NULL);
8934   }
8935           case WhitePromotion:
8936           case BlackPromotion:
8937           case WhiteNonPromotion:
8938           case BlackNonPromotion:
8939           case NormalMove:
8940           case WhiteCapturesEnPassant:
8941           case BlackCapturesEnPassant:
8942           case WhiteKingSideCastle:
8943           case WhiteQueenSideCastle:
8944           case BlackKingSideCastle:
8945           case BlackQueenSideCastle:
8946           case WhiteKingSideCastleWild:
8947           case WhiteQueenSideCastleWild:
8948           case BlackKingSideCastleWild:
8949           case BlackQueenSideCastleWild:
8950           /* PUSH Fabien */
8951           case WhiteHSideCastleFR:
8952           case WhiteASideCastleFR:
8953           case BlackHSideCastleFR:
8954           case BlackASideCastleFR:
8955           /* POP Fabien */
8956             fromX = currentMoveString[0] - AAA;
8957             fromY = currentMoveString[1] - ONE;
8958             toX = currentMoveString[2] - AAA;
8959             toY = currentMoveString[3] - ONE;
8960             promoChar = currentMoveString[4];
8961             break;
8962           case WhiteDrop:
8963           case BlackDrop:
8964             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8965             fromX = moveType == WhiteDrop ?
8966               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8967             (int) CharToPiece(ToLower(currentMoveString[0]));
8968             fromY = DROP_RANK;
8969             toX = currentMoveString[2] - AAA;
8970             toY = currentMoveString[3] - ONE;
8971             promoChar = NULLCHAR;
8972             break;
8973           case AmbiguousMove:
8974             /* bug? */
8975             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8976   if (appData.debugMode) {
8977     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8978     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8979     setbuf(debugFP, NULL);
8980   }
8981             DisplayError(buf, 0);
8982             return;
8983           case ImpossibleMove:
8984             /* bug? */
8985             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8986   if (appData.debugMode) {
8987     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8988     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8989     setbuf(debugFP, NULL);
8990   }
8991             DisplayError(buf, 0);
8992             return;
8993           case EndOfFile:
8994             if (boardIndex < backwardMostMove) {
8995                 /* Oops, gap.  How did that happen? */
8996                 DisplayError(_("Gap in move list"), 0);
8997                 return;
8998             }
8999             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9000             if (boardIndex > forwardMostMove) {
9001                 forwardMostMove = boardIndex;
9002             }
9003             return;
9004           case ElapsedTime:
9005             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9006                 strcat(parseList[boardIndex-1], " ");
9007                 strcat(parseList[boardIndex-1], yy_text);
9008             }
9009             continue;
9010           case Comment:
9011           case PGNTag:
9012           case NAG:
9013           default:
9014             /* ignore */
9015             continue;
9016           case WhiteWins:
9017           case BlackWins:
9018           case GameIsDrawn:
9019           case GameUnfinished:
9020             if (gameMode == IcsExamining) {
9021                 if (boardIndex < backwardMostMove) {
9022                     /* Oops, gap.  How did that happen? */
9023                     return;
9024                 }
9025                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9026                 return;
9027             }
9028             gameInfo.result = moveType;
9029             p = strchr(yy_text, '{');
9030             if (p == NULL) p = strchr(yy_text, '(');
9031             if (p == NULL) {
9032                 p = yy_text;
9033                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9034             } else {
9035                 q = strchr(p, *p == '{' ? '}' : ')');
9036                 if (q != NULL) *q = NULLCHAR;
9037                 p++;
9038             }
9039             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9040             gameInfo.resultDetails = StrSave(p);
9041             continue;
9042         }
9043         if (boardIndex >= forwardMostMove &&
9044             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9045             backwardMostMove = blackPlaysFirst ? 1 : 0;
9046             return;
9047         }
9048         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9049                                  fromY, fromX, toY, toX, promoChar,
9050                                  parseList[boardIndex]);
9051         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9052         /* currentMoveString is set as a side-effect of yylex */
9053         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9054         strcat(moveList[boardIndex], "\n");
9055         boardIndex++;
9056         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9057         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9058           case MT_NONE:
9059           case MT_STALEMATE:
9060           default:
9061             break;
9062           case MT_CHECK:
9063             if(gameInfo.variant != VariantShogi)
9064                 strcat(parseList[boardIndex - 1], "+");
9065             break;
9066           case MT_CHECKMATE:
9067           case MT_STAINMATE:
9068             strcat(parseList[boardIndex - 1], "#");
9069             break;
9070         }
9071     }
9072 }
9073
9074
9075 /* Apply a move to the given board  */
9076 void
9077 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9078      int fromX, fromY, toX, toY;
9079      int promoChar;
9080      Board board;
9081 {
9082   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9083   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9084
9085     /* [HGM] compute & store e.p. status and castling rights for new position */
9086     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9087
9088       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9089       oldEP = (signed char)board[EP_STATUS];
9090       board[EP_STATUS] = EP_NONE;
9091
9092   if (fromY == DROP_RANK) {
9093         /* must be first */
9094         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9095             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9096             return;
9097         }
9098         piece = board[toY][toX] = (ChessSquare) fromX;
9099   } else {
9100       int i;
9101
9102       if( board[toY][toX] != EmptySquare )
9103            board[EP_STATUS] = EP_CAPTURE;
9104
9105       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9106            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9107                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9108       } else
9109       if( board[fromY][fromX] == WhitePawn ) {
9110            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9111                board[EP_STATUS] = EP_PAWN_MOVE;
9112            if( toY-fromY==2) {
9113                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9114                         gameInfo.variant != VariantBerolina || toX < fromX)
9115                       board[EP_STATUS] = toX | berolina;
9116                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9117                         gameInfo.variant != VariantBerolina || toX > fromX)
9118                       board[EP_STATUS] = toX;
9119            }
9120       } else
9121       if( board[fromY][fromX] == BlackPawn ) {
9122            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9123                board[EP_STATUS] = EP_PAWN_MOVE;
9124            if( toY-fromY== -2) {
9125                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9126                         gameInfo.variant != VariantBerolina || toX < fromX)
9127                       board[EP_STATUS] = toX | berolina;
9128                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9129                         gameInfo.variant != VariantBerolina || toX > fromX)
9130                       board[EP_STATUS] = toX;
9131            }
9132        }
9133
9134        for(i=0; i<nrCastlingRights; i++) {
9135            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9136               board[CASTLING][i] == toX   && castlingRank[i] == toY
9137              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9138        }
9139
9140      if (fromX == toX && fromY == toY) return;
9141
9142      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9143      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9144      if(gameInfo.variant == VariantKnightmate)
9145          king += (int) WhiteUnicorn - (int) WhiteKing;
9146
9147     /* Code added by Tord: */
9148     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9149     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9150         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9151       board[fromY][fromX] = EmptySquare;
9152       board[toY][toX] = EmptySquare;
9153       if((toX > fromX) != (piece == WhiteRook)) {
9154         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9155       } else {
9156         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9157       }
9158     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9159                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9160       board[fromY][fromX] = EmptySquare;
9161       board[toY][toX] = EmptySquare;
9162       if((toX > fromX) != (piece == BlackRook)) {
9163         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9164       } else {
9165         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9166       }
9167     /* End of code added by Tord */
9168
9169     } else if (board[fromY][fromX] == king
9170         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9171         && toY == fromY && toX > fromX+1) {
9172         board[fromY][fromX] = EmptySquare;
9173         board[toY][toX] = king;
9174         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9175         board[fromY][BOARD_RGHT-1] = EmptySquare;
9176     } else if (board[fromY][fromX] == king
9177         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9178                && toY == fromY && toX < fromX-1) {
9179         board[fromY][fromX] = EmptySquare;
9180         board[toY][toX] = king;
9181         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9182         board[fromY][BOARD_LEFT] = EmptySquare;
9183     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9184                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9185                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9186                ) {
9187         /* white pawn promotion */
9188         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9189         if(gameInfo.variant==VariantBughouse ||
9190            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9191             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9192         board[fromY][fromX] = EmptySquare;
9193     } else if ((fromY >= BOARD_HEIGHT>>1)
9194                && (toX != fromX)
9195                && gameInfo.variant != VariantXiangqi
9196                && gameInfo.variant != VariantBerolina
9197                && (board[fromY][fromX] == WhitePawn)
9198                && (board[toY][toX] == EmptySquare)) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = WhitePawn;
9201         captured = board[toY - 1][toX];
9202         board[toY - 1][toX] = EmptySquare;
9203     } else if ((fromY == BOARD_HEIGHT-4)
9204                && (toX == fromX)
9205                && gameInfo.variant == VariantBerolina
9206                && (board[fromY][fromX] == WhitePawn)
9207                && (board[toY][toX] == EmptySquare)) {
9208         board[fromY][fromX] = EmptySquare;
9209         board[toY][toX] = WhitePawn;
9210         if(oldEP & EP_BEROLIN_A) {
9211                 captured = board[fromY][fromX-1];
9212                 board[fromY][fromX-1] = EmptySquare;
9213         }else{  captured = board[fromY][fromX+1];
9214                 board[fromY][fromX+1] = EmptySquare;
9215         }
9216     } else if (board[fromY][fromX] == king
9217         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9218                && toY == fromY && toX > fromX+1) {
9219         board[fromY][fromX] = EmptySquare;
9220         board[toY][toX] = king;
9221         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9222         board[fromY][BOARD_RGHT-1] = EmptySquare;
9223     } else if (board[fromY][fromX] == king
9224         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9225                && toY == fromY && toX < fromX-1) {
9226         board[fromY][fromX] = EmptySquare;
9227         board[toY][toX] = king;
9228         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9229         board[fromY][BOARD_LEFT] = EmptySquare;
9230     } else if (fromY == 7 && fromX == 3
9231                && board[fromY][fromX] == BlackKing
9232                && toY == 7 && toX == 5) {
9233         board[fromY][fromX] = EmptySquare;
9234         board[toY][toX] = BlackKing;
9235         board[fromY][7] = EmptySquare;
9236         board[toY][4] = BlackRook;
9237     } else if (fromY == 7 && fromX == 3
9238                && board[fromY][fromX] == BlackKing
9239                && toY == 7 && toX == 1) {
9240         board[fromY][fromX] = EmptySquare;
9241         board[toY][toX] = BlackKing;
9242         board[fromY][0] = EmptySquare;
9243         board[toY][2] = BlackRook;
9244     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9245                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9246                && toY < promoRank && promoChar
9247                ) {
9248         /* black pawn promotion */
9249         board[toY][toX] = CharToPiece(ToLower(promoChar));
9250         if(gameInfo.variant==VariantBughouse ||
9251            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9252             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9253         board[fromY][fromX] = EmptySquare;
9254     } else if ((fromY < BOARD_HEIGHT>>1)
9255                && (toX != fromX)
9256                && gameInfo.variant != VariantXiangqi
9257                && gameInfo.variant != VariantBerolina
9258                && (board[fromY][fromX] == BlackPawn)
9259                && (board[toY][toX] == EmptySquare)) {
9260         board[fromY][fromX] = EmptySquare;
9261         board[toY][toX] = BlackPawn;
9262         captured = board[toY + 1][toX];
9263         board[toY + 1][toX] = EmptySquare;
9264     } else if ((fromY == 3)
9265                && (toX == fromX)
9266                && gameInfo.variant == VariantBerolina
9267                && (board[fromY][fromX] == BlackPawn)
9268                && (board[toY][toX] == EmptySquare)) {
9269         board[fromY][fromX] = EmptySquare;
9270         board[toY][toX] = BlackPawn;
9271         if(oldEP & EP_BEROLIN_A) {
9272                 captured = board[fromY][fromX-1];
9273                 board[fromY][fromX-1] = EmptySquare;
9274         }else{  captured = board[fromY][fromX+1];
9275                 board[fromY][fromX+1] = EmptySquare;
9276         }
9277     } else {
9278         board[toY][toX] = board[fromY][fromX];
9279         board[fromY][fromX] = EmptySquare;
9280     }
9281   }
9282
9283     if (gameInfo.holdingsWidth != 0) {
9284
9285       /* !!A lot more code needs to be written to support holdings  */
9286       /* [HGM] OK, so I have written it. Holdings are stored in the */
9287       /* penultimate board files, so they are automaticlly stored   */
9288       /* in the game history.                                       */
9289       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9290                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9291         /* Delete from holdings, by decreasing count */
9292         /* and erasing image if necessary            */
9293         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9294         if(p < (int) BlackPawn) { /* white drop */
9295              p -= (int)WhitePawn;
9296                  p = PieceToNumber((ChessSquare)p);
9297              if(p >= gameInfo.holdingsSize) p = 0;
9298              if(--board[p][BOARD_WIDTH-2] <= 0)
9299                   board[p][BOARD_WIDTH-1] = EmptySquare;
9300              if((int)board[p][BOARD_WIDTH-2] < 0)
9301                         board[p][BOARD_WIDTH-2] = 0;
9302         } else {                  /* black drop */
9303              p -= (int)BlackPawn;
9304                  p = PieceToNumber((ChessSquare)p);
9305              if(p >= gameInfo.holdingsSize) p = 0;
9306              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9307                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9308              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9309                         board[BOARD_HEIGHT-1-p][1] = 0;
9310         }
9311       }
9312       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9313           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9314         /* [HGM] holdings: Add to holdings, if holdings exist */
9315         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9316                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9317                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9318         }
9319         p = (int) captured;
9320         if (p >= (int) BlackPawn) {
9321           p -= (int)BlackPawn;
9322           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9323                   /* in Shogi restore piece to its original  first */
9324                   captured = (ChessSquare) (DEMOTED captured);
9325                   p = DEMOTED p;
9326           }
9327           p = PieceToNumber((ChessSquare)p);
9328           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9329           board[p][BOARD_WIDTH-2]++;
9330           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9331         } else {
9332           p -= (int)WhitePawn;
9333           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9334                   captured = (ChessSquare) (DEMOTED captured);
9335                   p = DEMOTED p;
9336           }
9337           p = PieceToNumber((ChessSquare)p);
9338           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9339           board[BOARD_HEIGHT-1-p][1]++;
9340           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9341         }
9342       }
9343     } else if (gameInfo.variant == VariantAtomic) {
9344       if (captured != EmptySquare) {
9345         int y, x;
9346         for (y = toY-1; y <= toY+1; y++) {
9347           for (x = toX-1; x <= toX+1; x++) {
9348             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9349                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9350               board[y][x] = EmptySquare;
9351             }
9352           }
9353         }
9354         board[toY][toX] = EmptySquare;
9355       }
9356     }
9357     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9358         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9359     } else
9360     if(promoChar == '+') {
9361         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9362         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9363     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9364         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9365     }
9366     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9367                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9368         // [HGM] superchess: take promotion piece out of holdings
9369         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9370         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9371             if(!--board[k][BOARD_WIDTH-2])
9372                 board[k][BOARD_WIDTH-1] = EmptySquare;
9373         } else {
9374             if(!--board[BOARD_HEIGHT-1-k][1])
9375                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9376         }
9377     }
9378
9379 }
9380
9381 /* Updates forwardMostMove */
9382 void
9383 MakeMove(fromX, fromY, toX, toY, promoChar)
9384      int fromX, fromY, toX, toY;
9385      int promoChar;
9386 {
9387 //    forwardMostMove++; // [HGM] bare: moved downstream
9388
9389     (void) CoordsToAlgebraic(boards[forwardMostMove],
9390                              PosFlags(forwardMostMove),
9391                              fromY, fromX, toY, toX, promoChar,
9392                              parseList[forwardMostMove]);
9393
9394     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9395         int timeLeft; static int lastLoadFlag=0; int king, piece;
9396         piece = boards[forwardMostMove][fromY][fromX];
9397         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9398         if(gameInfo.variant == VariantKnightmate)
9399             king += (int) WhiteUnicorn - (int) WhiteKing;
9400         if(forwardMostMove == 0) {
9401             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9402                 fprintf(serverMoves, "%s;", UserName());
9403             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9404                 fprintf(serverMoves, "%s;", second.tidy);
9405             fprintf(serverMoves, "%s;", first.tidy);
9406             if(gameMode == MachinePlaysWhite)
9407                 fprintf(serverMoves, "%s;", UserName());
9408             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9409                 fprintf(serverMoves, "%s;", second.tidy);
9410         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9411         lastLoadFlag = loadFlag;
9412         // print base move
9413         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9414         // print castling suffix
9415         if( toY == fromY && piece == king ) {
9416             if(toX-fromX > 1)
9417                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9418             if(fromX-toX >1)
9419                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9420         }
9421         // e.p. suffix
9422         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9423              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9424              boards[forwardMostMove][toY][toX] == EmptySquare
9425              && fromX != toX && fromY != toY)
9426                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9427         // promotion suffix
9428         if(promoChar != NULLCHAR)
9429                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9430         if(!loadFlag) {
9431                 char buf[MOVE_LEN*2], *p; int len;
9432             fprintf(serverMoves, "/%d/%d",
9433                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9434             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9435             else                      timeLeft = blackTimeRemaining/1000;
9436             fprintf(serverMoves, "/%d", timeLeft);
9437                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9438                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9439                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9440             fprintf(serverMoves, "/%s", buf);
9441         }
9442         fflush(serverMoves);
9443     }
9444
9445     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9446         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9447       return;
9448     }
9449     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9450     if (commentList[forwardMostMove+1] != NULL) {
9451         free(commentList[forwardMostMove+1]);
9452         commentList[forwardMostMove+1] = NULL;
9453     }
9454     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9455     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9456     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9457     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9458     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9459     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9460     adjustedClock = FALSE;
9461     gameInfo.result = GameUnfinished;
9462     if (gameInfo.resultDetails != NULL) {
9463         free(gameInfo.resultDetails);
9464         gameInfo.resultDetails = NULL;
9465     }
9466     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9467                               moveList[forwardMostMove - 1]);
9468     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9469       case MT_NONE:
9470       case MT_STALEMATE:
9471       default:
9472         break;
9473       case MT_CHECK:
9474         if(gameInfo.variant != VariantShogi)
9475             strcat(parseList[forwardMostMove - 1], "+");
9476         break;
9477       case MT_CHECKMATE:
9478       case MT_STAINMATE:
9479         strcat(parseList[forwardMostMove - 1], "#");
9480         break;
9481     }
9482     if (appData.debugMode) {
9483         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9484     }
9485
9486 }
9487
9488 /* Updates currentMove if not pausing */
9489 void
9490 ShowMove(fromX, fromY, toX, toY)
9491 {
9492     int instant = (gameMode == PlayFromGameFile) ?
9493         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9494     if(appData.noGUI) return;
9495     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9496         if (!instant) {
9497             if (forwardMostMove == currentMove + 1) {
9498                 AnimateMove(boards[forwardMostMove - 1],
9499                             fromX, fromY, toX, toY);
9500             }
9501             if (appData.highlightLastMove) {
9502                 SetHighlights(fromX, fromY, toX, toY);
9503             }
9504         }
9505         currentMove = forwardMostMove;
9506     }
9507
9508     if (instant) return;
9509
9510     DisplayMove(currentMove - 1);
9511     DrawPosition(FALSE, boards[currentMove]);
9512     DisplayBothClocks();
9513     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9514 }
9515
9516 void SendEgtPath(ChessProgramState *cps)
9517 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9518         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9519
9520         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9521
9522         while(*p) {
9523             char c, *q = name+1, *r, *s;
9524
9525             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9526             while(*p && *p != ',') *q++ = *p++;
9527             *q++ = ':'; *q = 0;
9528             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9529                 strcmp(name, ",nalimov:") == 0 ) {
9530                 // take nalimov path from the menu-changeable option first, if it is defined
9531               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9532                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9533             } else
9534             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9535                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9536                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9537                 s = r = StrStr(s, ":") + 1; // beginning of path info
9538                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9539                 c = *r; *r = 0;             // temporarily null-terminate path info
9540                     *--q = 0;               // strip of trailig ':' from name
9541                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9542                 *r = c;
9543                 SendToProgram(buf,cps);     // send egtbpath command for this format
9544             }
9545             if(*p == ',') p++; // read away comma to position for next format name
9546         }
9547 }
9548
9549 void
9550 InitChessProgram(cps, setup)
9551      ChessProgramState *cps;
9552      int setup; /* [HGM] needed to setup FRC opening position */
9553 {
9554     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9555     if (appData.noChessProgram) return;
9556     hintRequested = FALSE;
9557     bookRequested = FALSE;
9558
9559     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9560     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9561     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9562     if(cps->memSize) { /* [HGM] memory */
9563       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9564         SendToProgram(buf, cps);
9565     }
9566     SendEgtPath(cps); /* [HGM] EGT */
9567     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9568       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9569         SendToProgram(buf, cps);
9570     }
9571
9572     SendToProgram(cps->initString, cps);
9573     if (gameInfo.variant != VariantNormal &&
9574         gameInfo.variant != VariantLoadable
9575         /* [HGM] also send variant if board size non-standard */
9576         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9577                                             ) {
9578       char *v = VariantName(gameInfo.variant);
9579       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9580         /* [HGM] in protocol 1 we have to assume all variants valid */
9581         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9582         DisplayFatalError(buf, 0, 1);
9583         return;
9584       }
9585
9586       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9587       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9588       if( gameInfo.variant == VariantXiangqi )
9589            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9590       if( gameInfo.variant == VariantShogi )
9591            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9592       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9593            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9594       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9595           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9596            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9597       if( gameInfo.variant == VariantCourier )
9598            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9599       if( gameInfo.variant == VariantSuper )
9600            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9601       if( gameInfo.variant == VariantGreat )
9602            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9603       if( gameInfo.variant == VariantSChess )
9604            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9605       if( gameInfo.variant == VariantGrand )
9606            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9607
9608       if(overruled) {
9609         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9610                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9611            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9612            if(StrStr(cps->variants, b) == NULL) {
9613                // specific sized variant not known, check if general sizing allowed
9614                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9615                    if(StrStr(cps->variants, "boardsize") == NULL) {
9616                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9617                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9618                        DisplayFatalError(buf, 0, 1);
9619                        return;
9620                    }
9621                    /* [HGM] here we really should compare with the maximum supported board size */
9622                }
9623            }
9624       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9625       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9626       SendToProgram(buf, cps);
9627     }
9628     currentlyInitializedVariant = gameInfo.variant;
9629
9630     /* [HGM] send opening position in FRC to first engine */
9631     if(setup) {
9632           SendToProgram("force\n", cps);
9633           SendBoard(cps, 0);
9634           /* engine is now in force mode! Set flag to wake it up after first move. */
9635           setboardSpoiledMachineBlack = 1;
9636     }
9637
9638     if (cps->sendICS) {
9639       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9640       SendToProgram(buf, cps);
9641     }
9642     cps->maybeThinking = FALSE;
9643     cps->offeredDraw = 0;
9644     if (!appData.icsActive) {
9645         SendTimeControl(cps, movesPerSession, timeControl,
9646                         timeIncrement, appData.searchDepth,
9647                         searchTime);
9648     }
9649     if (appData.showThinking
9650         // [HGM] thinking: four options require thinking output to be sent
9651         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9652                                 ) {
9653         SendToProgram("post\n", cps);
9654     }
9655     SendToProgram("hard\n", cps);
9656     if (!appData.ponderNextMove) {
9657         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9658            it without being sure what state we are in first.  "hard"
9659            is not a toggle, so that one is OK.
9660          */
9661         SendToProgram("easy\n", cps);
9662     }
9663     if (cps->usePing) {
9664       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9665       SendToProgram(buf, cps);
9666     }
9667     cps->initDone = TRUE;
9668     ClearEngineOutputPane(cps == &second);
9669 }
9670
9671
9672 void
9673 StartChessProgram(cps)
9674      ChessProgramState *cps;
9675 {
9676     char buf[MSG_SIZ];
9677     int err;
9678
9679     if (appData.noChessProgram) return;
9680     cps->initDone = FALSE;
9681
9682     if (strcmp(cps->host, "localhost") == 0) {
9683         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9684     } else if (*appData.remoteShell == NULLCHAR) {
9685         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9686     } else {
9687         if (*appData.remoteUser == NULLCHAR) {
9688           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9689                     cps->program);
9690         } else {
9691           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9692                     cps->host, appData.remoteUser, cps->program);
9693         }
9694         err = StartChildProcess(buf, "", &cps->pr);
9695     }
9696
9697     if (err != 0) {
9698       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9699         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9700         if(cps != &first) return;
9701         appData.noChessProgram = TRUE;
9702         ThawUI();
9703         SetNCPMode();
9704 //      DisplayFatalError(buf, err, 1);
9705 //      cps->pr = NoProc;
9706 //      cps->isr = NULL;
9707         return;
9708     }
9709
9710     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9711     if (cps->protocolVersion > 1) {
9712       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9713       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9714       cps->comboCnt = 0;  //                and values of combo boxes
9715       SendToProgram(buf, cps);
9716     } else {
9717       SendToProgram("xboard\n", cps);
9718     }
9719 }
9720
9721 void
9722 TwoMachinesEventIfReady P((void))
9723 {
9724   static int curMess = 0;
9725   if (first.lastPing != first.lastPong) {
9726     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9727     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9728     return;
9729   }
9730   if (second.lastPing != second.lastPong) {
9731     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9732     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9733     return;
9734   }
9735   DisplayMessage("", ""); curMess = 0;
9736   ThawUI();
9737   TwoMachinesEvent();
9738 }
9739
9740 char *
9741 MakeName(char *template)
9742 {
9743     time_t clock;
9744     struct tm *tm;
9745     static char buf[MSG_SIZ];
9746     char *p = buf;
9747     int i;
9748
9749     clock = time((time_t *)NULL);
9750     tm = localtime(&clock);
9751
9752     while(*p++ = *template++) if(p[-1] == '%') {
9753         switch(*template++) {
9754           case 0:   *p = 0; return buf;
9755           case 'Y': i = tm->tm_year+1900; break;
9756           case 'y': i = tm->tm_year-100; break;
9757           case 'M': i = tm->tm_mon+1; break;
9758           case 'd': i = tm->tm_mday; break;
9759           case 'h': i = tm->tm_hour; break;
9760           case 'm': i = tm->tm_min; break;
9761           case 's': i = tm->tm_sec; break;
9762           default:  i = 0;
9763         }
9764         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9765     }
9766     return buf;
9767 }
9768
9769 int
9770 CountPlayers(char *p)
9771 {
9772     int n = 0;
9773     while(p = strchr(p, '\n')) p++, n++; // count participants
9774     return n;
9775 }
9776
9777 FILE *
9778 WriteTourneyFile(char *results, FILE *f)
9779 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9780     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9781     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9782         // create a file with tournament description
9783         fprintf(f, "-participants {%s}\n", appData.participants);
9784         fprintf(f, "-seedBase %d\n", appData.seedBase);
9785         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9786         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9787         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9788         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9789         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9790         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9791         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9792         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9793         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9794         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9795         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9796         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9797         if(searchTime > 0)
9798                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9799         else {
9800                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9801                 fprintf(f, "-tc %s\n", appData.timeControl);
9802                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9803         }
9804         fprintf(f, "-results \"%s\"\n", results);
9805     }
9806     return f;
9807 }
9808
9809 #define MAXENGINES 1000
9810 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9811
9812 void Substitute(char *participants, int expunge)
9813 {
9814     int i, changed, changes=0, nPlayers=0;
9815     char *p, *q, *r, buf[MSG_SIZ];
9816     if(participants == NULL) return;
9817     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9818     r = p = participants; q = appData.participants;
9819     while(*p && *p == *q) {
9820         if(*p == '\n') r = p+1, nPlayers++;
9821         p++; q++;
9822     }
9823     if(*p) { // difference
9824         while(*p && *p++ != '\n');
9825         while(*q && *q++ != '\n');
9826       changed = nPlayers;
9827         changes = 1 + (strcmp(p, q) != 0);
9828     }
9829     if(changes == 1) { // a single engine mnemonic was changed
9830         q = r; while(*q) nPlayers += (*q++ == '\n');
9831         p = buf; while(*r && (*p = *r++) != '\n') p++;
9832         *p = NULLCHAR;
9833         NamesToList(firstChessProgramNames, command, mnemonic);
9834         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9835         if(mnemonic[i]) { // The substitute is valid
9836             FILE *f;
9837             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9838                 flock(fileno(f), LOCK_EX);
9839                 ParseArgsFromFile(f);
9840                 fseek(f, 0, SEEK_SET);
9841                 FREE(appData.participants); appData.participants = participants;
9842                 if(expunge) { // erase results of replaced engine
9843                     int len = strlen(appData.results), w, b, dummy;
9844                     for(i=0; i<len; i++) {
9845                         Pairing(i, nPlayers, &w, &b, &dummy);
9846                         if((w == changed || b == changed) && appData.results[i] == '*') {
9847                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9848                             fclose(f);
9849                             return;
9850                         }
9851                     }
9852                     for(i=0; i<len; i++) {
9853                         Pairing(i, nPlayers, &w, &b, &dummy);
9854                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9855                     }
9856                 }
9857                 WriteTourneyFile(appData.results, f);
9858                 fclose(f); // release lock
9859                 return;
9860             }
9861         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9862     }
9863     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9864     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9865     free(participants);
9866     return;
9867 }
9868
9869 int
9870 CreateTourney(char *name)
9871 {
9872         FILE *f;
9873         if(matchMode && strcmp(name, appData.tourneyFile)) {
9874              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9875         }
9876         if(name[0] == NULLCHAR) {
9877             if(appData.participants[0])
9878                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9879             return 0;
9880         }
9881         f = fopen(name, "r");
9882         if(f) { // file exists
9883             ASSIGN(appData.tourneyFile, name);
9884             ParseArgsFromFile(f); // parse it
9885         } else {
9886             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9887             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9888                 DisplayError(_("Not enough participants"), 0);
9889                 return 0;
9890             }
9891             ASSIGN(appData.tourneyFile, name);
9892             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9893             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9894         }
9895         fclose(f);
9896         appData.noChessProgram = FALSE;
9897         appData.clockMode = TRUE;
9898         SetGNUMode();
9899         return 1;
9900 }
9901
9902 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9903 {
9904     char buf[MSG_SIZ], *p, *q;
9905     int i=1;
9906     while(*names) {
9907         p = names; q = buf;
9908         while(*p && *p != '\n') *q++ = *p++;
9909         *q = 0;
9910         if(engineList[i]) free(engineList[i]);
9911         engineList[i] = strdup(buf);
9912         if(*p == '\n') p++;
9913         TidyProgramName(engineList[i], "localhost", buf);
9914         if(engineMnemonic[i]) free(engineMnemonic[i]);
9915         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9916             strcat(buf, " (");
9917             sscanf(q + 8, "%s", buf + strlen(buf));
9918             strcat(buf, ")");
9919         }
9920         engineMnemonic[i] = strdup(buf);
9921         names = p; i++;
9922       if(i > MAXENGINES - 2) break;
9923     }
9924     engineList[i] = engineMnemonic[i] = NULL;
9925 }
9926
9927 // following implemented as macro to avoid type limitations
9928 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9929
9930 void SwapEngines(int n)
9931 {   // swap settings for first engine and other engine (so far only some selected options)
9932     int h;
9933     char *p;
9934     if(n == 0) return;
9935     SWAP(directory, p)
9936     SWAP(chessProgram, p)
9937     SWAP(isUCI, h)
9938     SWAP(hasOwnBookUCI, h)
9939     SWAP(protocolVersion, h)
9940     SWAP(reuse, h)
9941     SWAP(scoreIsAbsolute, h)
9942     SWAP(timeOdds, h)
9943     SWAP(logo, p)
9944     SWAP(pgnName, p)
9945     SWAP(pvSAN, h)
9946     SWAP(engOptions, p)
9947 }
9948
9949 void
9950 SetPlayer(int player)
9951 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9952     int i;
9953     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9954     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9955     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9956     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9957     if(mnemonic[i]) {
9958         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9959         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9960         appData.firstHasOwnBookUCI = !appData.defNoBook;
9961         ParseArgsFromString(buf);
9962     }
9963     free(engineName);
9964 }
9965
9966 int
9967 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9968 {   // determine players from game number
9969     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9970
9971     if(appData.tourneyType == 0) {
9972         roundsPerCycle = (nPlayers - 1) | 1;
9973         pairingsPerRound = nPlayers / 2;
9974     } else if(appData.tourneyType > 0) {
9975         roundsPerCycle = nPlayers - appData.tourneyType;
9976         pairingsPerRound = appData.tourneyType;
9977     }
9978     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9979     gamesPerCycle = gamesPerRound * roundsPerCycle;
9980     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9981     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9982     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9983     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9984     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9985     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9986
9987     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9988     if(appData.roundSync) *syncInterval = gamesPerRound;
9989
9990     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9991
9992     if(appData.tourneyType == 0) {
9993         if(curPairing == (nPlayers-1)/2 ) {
9994             *whitePlayer = curRound;
9995             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9996         } else {
9997             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9998             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9999             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10000             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10001         }
10002     } else if(appData.tourneyType > 0) {
10003         *whitePlayer = curPairing;
10004         *blackPlayer = curRound + appData.tourneyType;
10005     }
10006
10007     // take care of white/black alternation per round. 
10008     // For cycles and games this is already taken care of by default, derived from matchGame!
10009     return curRound & 1;
10010 }
10011
10012 int
10013 NextTourneyGame(int nr, int *swapColors)
10014 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10015     char *p, *q;
10016     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10017     FILE *tf;
10018     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10019     tf = fopen(appData.tourneyFile, "r");
10020     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10021     ParseArgsFromFile(tf); fclose(tf);
10022     InitTimeControls(); // TC might be altered from tourney file
10023
10024     nPlayers = CountPlayers(appData.participants); // count participants
10025     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10026     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10027
10028     if(syncInterval) {
10029         p = q = appData.results;
10030         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10031         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10032             DisplayMessage(_("Waiting for other game(s)"),"");
10033             waitingForGame = TRUE;
10034             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10035             return 0;
10036         }
10037         waitingForGame = FALSE;
10038     }
10039
10040     if(appData.tourneyType < 0) {
10041         if(nr>=0 && !pairingReceived) {
10042             char buf[1<<16];
10043             if(pairing.pr == NoProc) {
10044                 if(!appData.pairingEngine[0]) {
10045                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10046                     return 0;
10047                 }
10048                 StartChessProgram(&pairing); // starts the pairing engine
10049             }
10050             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10051             SendToProgram(buf, &pairing);
10052             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10053             SendToProgram(buf, &pairing);
10054             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10055         }
10056         pairingReceived = 0;                              // ... so we continue here 
10057         *swapColors = 0;
10058         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10059         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10060         matchGame = 1; roundNr = nr / syncInterval + 1;
10061     }
10062
10063     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10064
10065     // redefine engines, engine dir, etc.
10066     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10067     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10068     SwapEngines(1);
10069     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10070     SwapEngines(1);         // and make that valid for second engine by swapping
10071     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10072     InitEngine(&second, 1);
10073     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10074     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10075     return 1;
10076 }
10077
10078 void
10079 NextMatchGame()
10080 {   // performs game initialization that does not invoke engines, and then tries to start the game
10081     int res, firstWhite, swapColors = 0;
10082     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10083     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10084     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10085     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10086     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10087     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10088     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10089     Reset(FALSE, first.pr != NoProc);
10090     res = LoadGameOrPosition(matchGame); // setup game
10091     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10092     if(!res) return; // abort when bad game/pos file
10093     TwoMachinesEvent();
10094 }
10095
10096 void UserAdjudicationEvent( int result )
10097 {
10098     ChessMove gameResult = GameIsDrawn;
10099
10100     if( result > 0 ) {
10101         gameResult = WhiteWins;
10102     }
10103     else if( result < 0 ) {
10104         gameResult = BlackWins;
10105     }
10106
10107     if( gameMode == TwoMachinesPlay ) {
10108         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10109     }
10110 }
10111
10112
10113 // [HGM] save: calculate checksum of game to make games easily identifiable
10114 int StringCheckSum(char *s)
10115 {
10116         int i = 0;
10117         if(s==NULL) return 0;
10118         while(*s) i = i*259 + *s++;
10119         return i;
10120 }
10121
10122 int GameCheckSum()
10123 {
10124         int i, sum=0;
10125         for(i=backwardMostMove; i<forwardMostMove; i++) {
10126                 sum += pvInfoList[i].depth;
10127                 sum += StringCheckSum(parseList[i]);
10128                 sum += StringCheckSum(commentList[i]);
10129                 sum *= 261;
10130         }
10131         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10132         return sum + StringCheckSum(commentList[i]);
10133 } // end of save patch
10134
10135 void
10136 GameEnds(result, resultDetails, whosays)
10137      ChessMove result;
10138      char *resultDetails;
10139      int whosays;
10140 {
10141     GameMode nextGameMode;
10142     int isIcsGame;
10143     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10144
10145     if(endingGame) return; /* [HGM] crash: forbid recursion */
10146     endingGame = 1;
10147     if(twoBoards) { // [HGM] dual: switch back to one board
10148         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10149         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10150     }
10151     if (appData.debugMode) {
10152       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10153               result, resultDetails ? resultDetails : "(null)", whosays);
10154     }
10155
10156     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10157
10158     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10159         /* If we are playing on ICS, the server decides when the
10160            game is over, but the engine can offer to draw, claim
10161            a draw, or resign.
10162          */
10163 #if ZIPPY
10164         if (appData.zippyPlay && first.initDone) {
10165             if (result == GameIsDrawn) {
10166                 /* In case draw still needs to be claimed */
10167                 SendToICS(ics_prefix);
10168                 SendToICS("draw\n");
10169             } else if (StrCaseStr(resultDetails, "resign")) {
10170                 SendToICS(ics_prefix);
10171                 SendToICS("resign\n");
10172             }
10173         }
10174 #endif
10175         endingGame = 0; /* [HGM] crash */
10176         return;
10177     }
10178
10179     /* If we're loading the game from a file, stop */
10180     if (whosays == GE_FILE) {
10181       (void) StopLoadGameTimer();
10182       gameFileFP = NULL;
10183     }
10184
10185     /* Cancel draw offers */
10186     first.offeredDraw = second.offeredDraw = 0;
10187
10188     /* If this is an ICS game, only ICS can really say it's done;
10189        if not, anyone can. */
10190     isIcsGame = (gameMode == IcsPlayingWhite ||
10191                  gameMode == IcsPlayingBlack ||
10192                  gameMode == IcsObserving    ||
10193                  gameMode == IcsExamining);
10194
10195     if (!isIcsGame || whosays == GE_ICS) {
10196         /* OK -- not an ICS game, or ICS said it was done */
10197         StopClocks();
10198         if (!isIcsGame && !appData.noChessProgram)
10199           SetUserThinkingEnables();
10200
10201         /* [HGM] if a machine claims the game end we verify this claim */
10202         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10203             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10204                 char claimer;
10205                 ChessMove trueResult = (ChessMove) -1;
10206
10207                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10208                                             first.twoMachinesColor[0] :
10209                                             second.twoMachinesColor[0] ;
10210
10211                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10212                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10213                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10214                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10215                 } else
10216                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10217                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10218                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10219                 } else
10220                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10221                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10222                 }
10223
10224                 // now verify win claims, but not in drop games, as we don't understand those yet
10225                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10226                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10227                     (result == WhiteWins && claimer == 'w' ||
10228                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10229                       if (appData.debugMode) {
10230                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10231                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10232                       }
10233                       if(result != trueResult) {
10234                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10235                               result = claimer == 'w' ? BlackWins : WhiteWins;
10236                               resultDetails = buf;
10237                       }
10238                 } else
10239                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10240                     && (forwardMostMove <= backwardMostMove ||
10241                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10242                         (claimer=='b')==(forwardMostMove&1))
10243                                                                                   ) {
10244                       /* [HGM] verify: draws that were not flagged are false claims */
10245                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10246                       result = claimer == 'w' ? BlackWins : WhiteWins;
10247                       resultDetails = buf;
10248                 }
10249                 /* (Claiming a loss is accepted no questions asked!) */
10250             }
10251             /* [HGM] bare: don't allow bare King to win */
10252             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10253                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10254                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10255                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10256                && result != GameIsDrawn)
10257             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10258                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10259                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10260                         if(p >= 0 && p <= (int)WhiteKing) k++;
10261                 }
10262                 if (appData.debugMode) {
10263                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10264                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10265                 }
10266                 if(k <= 1) {
10267                         result = GameIsDrawn;
10268                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10269                         resultDetails = buf;
10270                 }
10271             }
10272         }
10273
10274
10275         if(serverMoves != NULL && !loadFlag) { char c = '=';
10276             if(result==WhiteWins) c = '+';
10277             if(result==BlackWins) c = '-';
10278             if(resultDetails != NULL)
10279                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10280         }
10281         if (resultDetails != NULL) {
10282             gameInfo.result = result;
10283             gameInfo.resultDetails = StrSave(resultDetails);
10284
10285             /* display last move only if game was not loaded from file */
10286             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10287                 DisplayMove(currentMove - 1);
10288
10289             if (forwardMostMove != 0) {
10290                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10291                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10292                                                                 ) {
10293                     if (*appData.saveGameFile != NULLCHAR) {
10294                         SaveGameToFile(appData.saveGameFile, TRUE);
10295                     } else if (appData.autoSaveGames) {
10296                         AutoSaveGame();
10297                     }
10298                     if (*appData.savePositionFile != NULLCHAR) {
10299                         SavePositionToFile(appData.savePositionFile);
10300                     }
10301                 }
10302             }
10303
10304             /* Tell program how game ended in case it is learning */
10305             /* [HGM] Moved this to after saving the PGN, just in case */
10306             /* engine died and we got here through time loss. In that */
10307             /* case we will get a fatal error writing the pipe, which */
10308             /* would otherwise lose us the PGN.                       */
10309             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10310             /* output during GameEnds should never be fatal anymore   */
10311             if (gameMode == MachinePlaysWhite ||
10312                 gameMode == MachinePlaysBlack ||
10313                 gameMode == TwoMachinesPlay ||
10314                 gameMode == IcsPlayingWhite ||
10315                 gameMode == IcsPlayingBlack ||
10316                 gameMode == BeginningOfGame) {
10317                 char buf[MSG_SIZ];
10318                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10319                         resultDetails);
10320                 if (first.pr != NoProc) {
10321                     SendToProgram(buf, &first);
10322                 }
10323                 if (second.pr != NoProc &&
10324                     gameMode == TwoMachinesPlay) {
10325                     SendToProgram(buf, &second);
10326                 }
10327             }
10328         }
10329
10330         if (appData.icsActive) {
10331             if (appData.quietPlay &&
10332                 (gameMode == IcsPlayingWhite ||
10333                  gameMode == IcsPlayingBlack)) {
10334                 SendToICS(ics_prefix);
10335                 SendToICS("set shout 1\n");
10336             }
10337             nextGameMode = IcsIdle;
10338             ics_user_moved = FALSE;
10339             /* clean up premove.  It's ugly when the game has ended and the
10340              * premove highlights are still on the board.
10341              */
10342             if (gotPremove) {
10343               gotPremove = FALSE;
10344               ClearPremoveHighlights();
10345               DrawPosition(FALSE, boards[currentMove]);
10346             }
10347             if (whosays == GE_ICS) {
10348                 switch (result) {
10349                 case WhiteWins:
10350                     if (gameMode == IcsPlayingWhite)
10351                         PlayIcsWinSound();
10352                     else if(gameMode == IcsPlayingBlack)
10353                         PlayIcsLossSound();
10354                     break;
10355                 case BlackWins:
10356                     if (gameMode == IcsPlayingBlack)
10357                         PlayIcsWinSound();
10358                     else if(gameMode == IcsPlayingWhite)
10359                         PlayIcsLossSound();
10360                     break;
10361                 case GameIsDrawn:
10362                     PlayIcsDrawSound();
10363                     break;
10364                 default:
10365                     PlayIcsUnfinishedSound();
10366                 }
10367             }
10368         } else if (gameMode == EditGame ||
10369                    gameMode == PlayFromGameFile ||
10370                    gameMode == AnalyzeMode ||
10371                    gameMode == AnalyzeFile) {
10372             nextGameMode = gameMode;
10373         } else {
10374             nextGameMode = EndOfGame;
10375         }
10376         pausing = FALSE;
10377         ModeHighlight();
10378     } else {
10379         nextGameMode = gameMode;
10380     }
10381
10382     if (appData.noChessProgram) {
10383         gameMode = nextGameMode;
10384         ModeHighlight();
10385         endingGame = 0; /* [HGM] crash */
10386         return;
10387     }
10388
10389     if (first.reuse) {
10390         /* Put first chess program into idle state */
10391         if (first.pr != NoProc &&
10392             (gameMode == MachinePlaysWhite ||
10393              gameMode == MachinePlaysBlack ||
10394              gameMode == TwoMachinesPlay ||
10395              gameMode == IcsPlayingWhite ||
10396              gameMode == IcsPlayingBlack ||
10397              gameMode == BeginningOfGame)) {
10398             SendToProgram("force\n", &first);
10399             if (first.usePing) {
10400               char buf[MSG_SIZ];
10401               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10402               SendToProgram(buf, &first);
10403             }
10404         }
10405     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10406         /* Kill off first chess program */
10407         if (first.isr != NULL)
10408           RemoveInputSource(first.isr);
10409         first.isr = NULL;
10410
10411         if (first.pr != NoProc) {
10412             ExitAnalyzeMode();
10413             DoSleep( appData.delayBeforeQuit );
10414             SendToProgram("quit\n", &first);
10415             DoSleep( appData.delayAfterQuit );
10416             DestroyChildProcess(first.pr, first.useSigterm);
10417         }
10418         first.pr = NoProc;
10419     }
10420     if (second.reuse) {
10421         /* Put second chess program into idle state */
10422         if (second.pr != NoProc &&
10423             gameMode == TwoMachinesPlay) {
10424             SendToProgram("force\n", &second);
10425             if (second.usePing) {
10426               char buf[MSG_SIZ];
10427               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10428               SendToProgram(buf, &second);
10429             }
10430         }
10431     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10432         /* Kill off second chess program */
10433         if (second.isr != NULL)
10434           RemoveInputSource(second.isr);
10435         second.isr = NULL;
10436
10437         if (second.pr != NoProc) {
10438             DoSleep( appData.delayBeforeQuit );
10439             SendToProgram("quit\n", &second);
10440             DoSleep( appData.delayAfterQuit );
10441             DestroyChildProcess(second.pr, second.useSigterm);
10442         }
10443         second.pr = NoProc;
10444     }
10445
10446     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10447         char resChar = '=';
10448         switch (result) {
10449         case WhiteWins:
10450           resChar = '+';
10451           if (first.twoMachinesColor[0] == 'w') {
10452             first.matchWins++;
10453           } else {
10454             second.matchWins++;
10455           }
10456           break;
10457         case BlackWins:
10458           resChar = '-';
10459           if (first.twoMachinesColor[0] == 'b') {
10460             first.matchWins++;
10461           } else {
10462             second.matchWins++;
10463           }
10464           break;
10465         case GameUnfinished:
10466           resChar = ' ';
10467         default:
10468           break;
10469         }
10470
10471         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10472         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10473             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10474             ReserveGame(nextGame, resChar); // sets nextGame
10475             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10476             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10477         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10478
10479         if (nextGame <= appData.matchGames && !abortMatch) {
10480             gameMode = nextGameMode;
10481             matchGame = nextGame; // this will be overruled in tourney mode!
10482             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10483             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10484             endingGame = 0; /* [HGM] crash */
10485             return;
10486         } else {
10487             gameMode = nextGameMode;
10488             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10489                      first.tidy, second.tidy,
10490                      first.matchWins, second.matchWins,
10491                      appData.matchGames - (first.matchWins + second.matchWins));
10492             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10493             if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10494             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10495             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10496                 first.twoMachinesColor = "black\n";
10497                 second.twoMachinesColor = "white\n";
10498             } else {
10499                 first.twoMachinesColor = "white\n";
10500                 second.twoMachinesColor = "black\n";
10501             }
10502         }
10503     }
10504     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10505         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10506       ExitAnalyzeMode();
10507     gameMode = nextGameMode;
10508     ModeHighlight();
10509     endingGame = 0;  /* [HGM] crash */
10510     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10511         if(matchMode == TRUE) { // match through command line: exit with or without popup
10512             if(ranking) {
10513                 ToNrEvent(forwardMostMove);
10514                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10515                 else ExitEvent(0);
10516             } else DisplayFatalError(buf, 0, 0);
10517         } else { // match through menu; just stop, with or without popup
10518             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10519             ModeHighlight();
10520             if(ranking){
10521                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10522             } else DisplayNote(buf);
10523       }
10524       if(ranking) free(ranking);
10525     }
10526 }
10527
10528 /* Assumes program was just initialized (initString sent).
10529    Leaves program in force mode. */
10530 void
10531 FeedMovesToProgram(cps, upto)
10532      ChessProgramState *cps;
10533      int upto;
10534 {
10535     int i;
10536
10537     if (appData.debugMode)
10538       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10539               startedFromSetupPosition ? "position and " : "",
10540               backwardMostMove, upto, cps->which);
10541     if(currentlyInitializedVariant != gameInfo.variant) {
10542       char buf[MSG_SIZ];
10543         // [HGM] variantswitch: make engine aware of new variant
10544         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10545                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10546         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10547         SendToProgram(buf, cps);
10548         currentlyInitializedVariant = gameInfo.variant;
10549     }
10550     SendToProgram("force\n", cps);
10551     if (startedFromSetupPosition) {
10552         SendBoard(cps, backwardMostMove);
10553     if (appData.debugMode) {
10554         fprintf(debugFP, "feedMoves\n");
10555     }
10556     }
10557     for (i = backwardMostMove; i < upto; i++) {
10558         SendMoveToProgram(i, cps);
10559     }
10560 }
10561
10562
10563 int
10564 ResurrectChessProgram()
10565 {
10566      /* The chess program may have exited.
10567         If so, restart it and feed it all the moves made so far. */
10568     static int doInit = 0;
10569
10570     if (appData.noChessProgram) return 1;
10571
10572     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10573         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10574         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10575         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10576     } else {
10577         if (first.pr != NoProc) return 1;
10578         StartChessProgram(&first);
10579     }
10580     InitChessProgram(&first, FALSE);
10581     FeedMovesToProgram(&first, currentMove);
10582
10583     if (!first.sendTime) {
10584         /* can't tell gnuchess what its clock should read,
10585            so we bow to its notion. */
10586         ResetClocks();
10587         timeRemaining[0][currentMove] = whiteTimeRemaining;
10588         timeRemaining[1][currentMove] = blackTimeRemaining;
10589     }
10590
10591     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10592                 appData.icsEngineAnalyze) && first.analysisSupport) {
10593       SendToProgram("analyze\n", &first);
10594       first.analyzing = TRUE;
10595     }
10596     return 1;
10597 }
10598
10599 /*
10600  * Button procedures
10601  */
10602 void
10603 Reset(redraw, init)
10604      int redraw, init;
10605 {
10606     int i;
10607
10608     if (appData.debugMode) {
10609         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10610                 redraw, init, gameMode);
10611     }
10612     CleanupTail(); // [HGM] vari: delete any stored variations
10613     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10614     pausing = pauseExamInvalid = FALSE;
10615     startedFromSetupPosition = blackPlaysFirst = FALSE;
10616     firstMove = TRUE;
10617     whiteFlag = blackFlag = FALSE;
10618     userOfferedDraw = FALSE;
10619     hintRequested = bookRequested = FALSE;
10620     first.maybeThinking = FALSE;
10621     second.maybeThinking = FALSE;
10622     first.bookSuspend = FALSE; // [HGM] book
10623     second.bookSuspend = FALSE;
10624     thinkOutput[0] = NULLCHAR;
10625     lastHint[0] = NULLCHAR;
10626     ClearGameInfo(&gameInfo);
10627     gameInfo.variant = StringToVariant(appData.variant);
10628     ics_user_moved = ics_clock_paused = FALSE;
10629     ics_getting_history = H_FALSE;
10630     ics_gamenum = -1;
10631     white_holding[0] = black_holding[0] = NULLCHAR;
10632     ClearProgramStats();
10633     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10634
10635     ResetFrontEnd();
10636     ClearHighlights();
10637     flipView = appData.flipView;
10638     ClearPremoveHighlights();
10639     gotPremove = FALSE;
10640     alarmSounded = FALSE;
10641
10642     GameEnds(EndOfFile, NULL, GE_PLAYER);
10643     if(appData.serverMovesName != NULL) {
10644         /* [HGM] prepare to make moves file for broadcasting */
10645         clock_t t = clock();
10646         if(serverMoves != NULL) fclose(serverMoves);
10647         serverMoves = fopen(appData.serverMovesName, "r");
10648         if(serverMoves != NULL) {
10649             fclose(serverMoves);
10650             /* delay 15 sec before overwriting, so all clients can see end */
10651             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10652         }
10653         serverMoves = fopen(appData.serverMovesName, "w");
10654     }
10655
10656     ExitAnalyzeMode();
10657     gameMode = BeginningOfGame;
10658     ModeHighlight();
10659     if(appData.icsActive) gameInfo.variant = VariantNormal;
10660     currentMove = forwardMostMove = backwardMostMove = 0;
10661     InitPosition(redraw);
10662     for (i = 0; i < MAX_MOVES; i++) {
10663         if (commentList[i] != NULL) {
10664             free(commentList[i]);
10665             commentList[i] = NULL;
10666         }
10667     }
10668     ResetClocks();
10669     timeRemaining[0][0] = whiteTimeRemaining;
10670     timeRemaining[1][0] = blackTimeRemaining;
10671
10672     if (first.pr == NoProc) {
10673         StartChessProgram(&first);
10674     }
10675     if (init) {
10676             InitChessProgram(&first, startedFromSetupPosition);
10677     }
10678     DisplayTitle("");
10679     DisplayMessage("", "");
10680     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10681     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10682 }
10683
10684 void
10685 AutoPlayGameLoop()
10686 {
10687     for (;;) {
10688         if (!AutoPlayOneMove())
10689           return;
10690         if (matchMode || appData.timeDelay == 0)
10691           continue;
10692         if (appData.timeDelay < 0)
10693           return;
10694         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10695         break;
10696     }
10697 }
10698
10699
10700 int
10701 AutoPlayOneMove()
10702 {
10703     int fromX, fromY, toX, toY;
10704
10705     if (appData.debugMode) {
10706       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10707     }
10708
10709     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10710       return FALSE;
10711
10712     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10713       pvInfoList[currentMove].depth = programStats.depth;
10714       pvInfoList[currentMove].score = programStats.score;
10715       pvInfoList[currentMove].time  = 0;
10716       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10717     }
10718
10719     if (currentMove >= forwardMostMove) {
10720       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10721 //      gameMode = EndOfGame;
10722 //      ModeHighlight();
10723
10724       /* [AS] Clear current move marker at the end of a game */
10725       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10726
10727       return FALSE;
10728     }
10729
10730     toX = moveList[currentMove][2] - AAA;
10731     toY = moveList[currentMove][3] - ONE;
10732
10733     if (moveList[currentMove][1] == '@') {
10734         if (appData.highlightLastMove) {
10735             SetHighlights(-1, -1, toX, toY);
10736         }
10737     } else {
10738         fromX = moveList[currentMove][0] - AAA;
10739         fromY = moveList[currentMove][1] - ONE;
10740
10741         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10742
10743         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10744
10745         if (appData.highlightLastMove) {
10746             SetHighlights(fromX, fromY, toX, toY);
10747         }
10748     }
10749     DisplayMove(currentMove);
10750     SendMoveToProgram(currentMove++, &first);
10751     DisplayBothClocks();
10752     DrawPosition(FALSE, boards[currentMove]);
10753     // [HGM] PV info: always display, routine tests if empty
10754     DisplayComment(currentMove - 1, commentList[currentMove]);
10755     return TRUE;
10756 }
10757
10758
10759 int
10760 LoadGameOneMove(readAhead)
10761      ChessMove readAhead;
10762 {
10763     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10764     char promoChar = NULLCHAR;
10765     ChessMove moveType;
10766     char move[MSG_SIZ];
10767     char *p, *q;
10768
10769     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10770         gameMode != AnalyzeMode && gameMode != Training) {
10771         gameFileFP = NULL;
10772         return FALSE;
10773     }
10774
10775     yyboardindex = forwardMostMove;
10776     if (readAhead != EndOfFile) {
10777       moveType = readAhead;
10778     } else {
10779       if (gameFileFP == NULL)
10780           return FALSE;
10781       moveType = (ChessMove) Myylex();
10782     }
10783
10784     done = FALSE;
10785     switch (moveType) {
10786       case Comment:
10787         if (appData.debugMode)
10788           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10789         p = yy_text;
10790
10791         /* append the comment but don't display it */
10792         AppendComment(currentMove, p, FALSE);
10793         return TRUE;
10794
10795       case WhiteCapturesEnPassant:
10796       case BlackCapturesEnPassant:
10797       case WhitePromotion:
10798       case BlackPromotion:
10799       case WhiteNonPromotion:
10800       case BlackNonPromotion:
10801       case NormalMove:
10802       case WhiteKingSideCastle:
10803       case WhiteQueenSideCastle:
10804       case BlackKingSideCastle:
10805       case BlackQueenSideCastle:
10806       case WhiteKingSideCastleWild:
10807       case WhiteQueenSideCastleWild:
10808       case BlackKingSideCastleWild:
10809       case BlackQueenSideCastleWild:
10810       /* PUSH Fabien */
10811       case WhiteHSideCastleFR:
10812       case WhiteASideCastleFR:
10813       case BlackHSideCastleFR:
10814       case BlackASideCastleFR:
10815       /* POP Fabien */
10816         if (appData.debugMode)
10817           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10818         fromX = currentMoveString[0] - AAA;
10819         fromY = currentMoveString[1] - ONE;
10820         toX = currentMoveString[2] - AAA;
10821         toY = currentMoveString[3] - ONE;
10822         promoChar = currentMoveString[4];
10823         break;
10824
10825       case WhiteDrop:
10826       case BlackDrop:
10827         if (appData.debugMode)
10828           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10829         fromX = moveType == WhiteDrop ?
10830           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10831         (int) CharToPiece(ToLower(currentMoveString[0]));
10832         fromY = DROP_RANK;
10833         toX = currentMoveString[2] - AAA;
10834         toY = currentMoveString[3] - ONE;
10835         break;
10836
10837       case WhiteWins:
10838       case BlackWins:
10839       case GameIsDrawn:
10840       case GameUnfinished:
10841         if (appData.debugMode)
10842           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10843         p = strchr(yy_text, '{');
10844         if (p == NULL) p = strchr(yy_text, '(');
10845         if (p == NULL) {
10846             p = yy_text;
10847             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10848         } else {
10849             q = strchr(p, *p == '{' ? '}' : ')');
10850             if (q != NULL) *q = NULLCHAR;
10851             p++;
10852         }
10853         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10854         GameEnds(moveType, p, GE_FILE);
10855         done = TRUE;
10856         if (cmailMsgLoaded) {
10857             ClearHighlights();
10858             flipView = WhiteOnMove(currentMove);
10859             if (moveType == GameUnfinished) flipView = !flipView;
10860             if (appData.debugMode)
10861               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10862         }
10863         break;
10864
10865       case EndOfFile:
10866         if (appData.debugMode)
10867           fprintf(debugFP, "Parser hit end of file\n");
10868         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10869           case MT_NONE:
10870           case MT_CHECK:
10871             break;
10872           case MT_CHECKMATE:
10873           case MT_STAINMATE:
10874             if (WhiteOnMove(currentMove)) {
10875                 GameEnds(BlackWins, "Black mates", GE_FILE);
10876             } else {
10877                 GameEnds(WhiteWins, "White mates", GE_FILE);
10878             }
10879             break;
10880           case MT_STALEMATE:
10881             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10882             break;
10883         }
10884         done = TRUE;
10885         break;
10886
10887       case MoveNumberOne:
10888         if (lastLoadGameStart == GNUChessGame) {
10889             /* GNUChessGames have numbers, but they aren't move numbers */
10890             if (appData.debugMode)
10891               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10892                       yy_text, (int) moveType);
10893             return LoadGameOneMove(EndOfFile); /* tail recursion */
10894         }
10895         /* else fall thru */
10896
10897       case XBoardGame:
10898       case GNUChessGame:
10899       case PGNTag:
10900         /* Reached start of next game in file */
10901         if (appData.debugMode)
10902           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10903         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10904           case MT_NONE:
10905           case MT_CHECK:
10906             break;
10907           case MT_CHECKMATE:
10908           case MT_STAINMATE:
10909             if (WhiteOnMove(currentMove)) {
10910                 GameEnds(BlackWins, "Black mates", GE_FILE);
10911             } else {
10912                 GameEnds(WhiteWins, "White mates", GE_FILE);
10913             }
10914             break;
10915           case MT_STALEMATE:
10916             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10917             break;
10918         }
10919         done = TRUE;
10920         break;
10921
10922       case PositionDiagram:     /* should not happen; ignore */
10923       case ElapsedTime:         /* ignore */
10924       case NAG:                 /* ignore */
10925         if (appData.debugMode)
10926           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10927                   yy_text, (int) moveType);
10928         return LoadGameOneMove(EndOfFile); /* tail recursion */
10929
10930       case IllegalMove:
10931         if (appData.testLegality) {
10932             if (appData.debugMode)
10933               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10934             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10935                     (forwardMostMove / 2) + 1,
10936                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10937             DisplayError(move, 0);
10938             done = TRUE;
10939         } else {
10940             if (appData.debugMode)
10941               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10942                       yy_text, currentMoveString);
10943             fromX = currentMoveString[0] - AAA;
10944             fromY = currentMoveString[1] - ONE;
10945             toX = currentMoveString[2] - AAA;
10946             toY = currentMoveString[3] - ONE;
10947             promoChar = currentMoveString[4];
10948         }
10949         break;
10950
10951       case AmbiguousMove:
10952         if (appData.debugMode)
10953           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10954         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10955                 (forwardMostMove / 2) + 1,
10956                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10957         DisplayError(move, 0);
10958         done = TRUE;
10959         break;
10960
10961       default:
10962       case ImpossibleMove:
10963         if (appData.debugMode)
10964           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10965         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10966                 (forwardMostMove / 2) + 1,
10967                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10968         DisplayError(move, 0);
10969         done = TRUE;
10970         break;
10971     }
10972
10973     if (done) {
10974         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10975             DrawPosition(FALSE, boards[currentMove]);
10976             DisplayBothClocks();
10977             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10978               DisplayComment(currentMove - 1, commentList[currentMove]);
10979         }
10980         (void) StopLoadGameTimer();
10981         gameFileFP = NULL;
10982         cmailOldMove = forwardMostMove;
10983         return FALSE;
10984     } else {
10985         /* currentMoveString is set as a side-effect of yylex */
10986
10987         thinkOutput[0] = NULLCHAR;
10988         MakeMove(fromX, fromY, toX, toY, promoChar);
10989         currentMove = forwardMostMove;
10990         return TRUE;
10991     }
10992 }
10993
10994 /* Load the nth game from the given file */
10995 int
10996 LoadGameFromFile(filename, n, title, useList)
10997      char *filename;
10998      int n;
10999      char *title;
11000      /*Boolean*/ int useList;
11001 {
11002     FILE *f;
11003     char buf[MSG_SIZ];
11004
11005     if (strcmp(filename, "-") == 0) {
11006         f = stdin;
11007         title = "stdin";
11008     } else {
11009         f = fopen(filename, "rb");
11010         if (f == NULL) {
11011           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11012             DisplayError(buf, errno);
11013             return FALSE;
11014         }
11015     }
11016     if (fseek(f, 0, 0) == -1) {
11017         /* f is not seekable; probably a pipe */
11018         useList = FALSE;
11019     }
11020     if (useList && n == 0) {
11021         int error = GameListBuild(f);
11022         if (error) {
11023             DisplayError(_("Cannot build game list"), error);
11024         } else if (!ListEmpty(&gameList) &&
11025                    ((ListGame *) gameList.tailPred)->number > 1) {
11026             GameListPopUp(f, title);
11027             return TRUE;
11028         }
11029         GameListDestroy();
11030         n = 1;
11031     }
11032     if (n == 0) n = 1;
11033     return LoadGame(f, n, title, FALSE);
11034 }
11035
11036
11037 void
11038 MakeRegisteredMove()
11039 {
11040     int fromX, fromY, toX, toY;
11041     char promoChar;
11042     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11043         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11044           case CMAIL_MOVE:
11045           case CMAIL_DRAW:
11046             if (appData.debugMode)
11047               fprintf(debugFP, "Restoring %s for game %d\n",
11048                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11049
11050             thinkOutput[0] = NULLCHAR;
11051             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11052             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11053             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11054             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11055             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11056             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11057             MakeMove(fromX, fromY, toX, toY, promoChar);
11058             ShowMove(fromX, fromY, toX, toY);
11059
11060             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11061               case MT_NONE:
11062               case MT_CHECK:
11063                 break;
11064
11065               case MT_CHECKMATE:
11066               case MT_STAINMATE:
11067                 if (WhiteOnMove(currentMove)) {
11068                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11069                 } else {
11070                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11071                 }
11072                 break;
11073
11074               case MT_STALEMATE:
11075                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11076                 break;
11077             }
11078
11079             break;
11080
11081           case CMAIL_RESIGN:
11082             if (WhiteOnMove(currentMove)) {
11083                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11084             } else {
11085                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11086             }
11087             break;
11088
11089           case CMAIL_ACCEPT:
11090             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11091             break;
11092
11093           default:
11094             break;
11095         }
11096     }
11097
11098     return;
11099 }
11100
11101 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11102 int
11103 CmailLoadGame(f, gameNumber, title, useList)
11104      FILE *f;
11105      int gameNumber;
11106      char *title;
11107      int useList;
11108 {
11109     int retVal;
11110
11111     if (gameNumber > nCmailGames) {
11112         DisplayError(_("No more games in this message"), 0);
11113         return FALSE;
11114     }
11115     if (f == lastLoadGameFP) {
11116         int offset = gameNumber - lastLoadGameNumber;
11117         if (offset == 0) {
11118             cmailMsg[0] = NULLCHAR;
11119             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11120                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11121                 nCmailMovesRegistered--;
11122             }
11123             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11124             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11125                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11126             }
11127         } else {
11128             if (! RegisterMove()) return FALSE;
11129         }
11130     }
11131
11132     retVal = LoadGame(f, gameNumber, title, useList);
11133
11134     /* Make move registered during previous look at this game, if any */
11135     MakeRegisteredMove();
11136
11137     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11138         commentList[currentMove]
11139           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11140         DisplayComment(currentMove - 1, commentList[currentMove]);
11141     }
11142
11143     return retVal;
11144 }
11145
11146 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11147 int
11148 ReloadGame(offset)
11149      int offset;
11150 {
11151     int gameNumber = lastLoadGameNumber + offset;
11152     if (lastLoadGameFP == NULL) {
11153         DisplayError(_("No game has been loaded yet"), 0);
11154         return FALSE;
11155     }
11156     if (gameNumber <= 0) {
11157         DisplayError(_("Can't back up any further"), 0);
11158         return FALSE;
11159     }
11160     if (cmailMsgLoaded) {
11161         return CmailLoadGame(lastLoadGameFP, gameNumber,
11162                              lastLoadGameTitle, lastLoadGameUseList);
11163     } else {
11164         return LoadGame(lastLoadGameFP, gameNumber,
11165                         lastLoadGameTitle, lastLoadGameUseList);
11166     }
11167 }
11168
11169 int keys[EmptySquare+1];
11170
11171 int
11172 PositionMatches(Board b1, Board b2)
11173 {
11174     int r, f, sum=0;
11175     switch(appData.searchMode) {
11176         case 1: return CompareWithRights(b1, b2);
11177         case 2:
11178             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11179                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11180             }
11181             return TRUE;
11182         case 3:
11183             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11184               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11185                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11186             }
11187             return sum==0;
11188         case 4:
11189             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11190                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11191             }
11192             return sum==0;
11193     }
11194     return TRUE;
11195 }
11196
11197 #define Q_PROMO  4
11198 #define Q_EP     3
11199 #define Q_BCASTL 2
11200 #define Q_WCASTL 1
11201
11202 int pieceList[256], quickBoard[256];
11203 ChessSquare pieceType[256] = { EmptySquare };
11204 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11205 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11206 int soughtTotal, turn;
11207 Boolean epOK, flipSearch;
11208
11209 typedef struct {
11210     unsigned char piece, to;
11211 } Move;
11212
11213 #define DSIZE (250000)
11214
11215 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11216 Move *moveDatabase = initialSpace;
11217 unsigned int movePtr, dataSize = DSIZE;
11218
11219 int MakePieceList(Board board, int *counts)
11220 {
11221     int r, f, n=Q_PROMO, total=0;
11222     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11223     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11224         int sq = f + (r<<4);
11225         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11226             quickBoard[sq] = ++n;
11227             pieceList[n] = sq;
11228             pieceType[n] = board[r][f];
11229             counts[board[r][f]]++;
11230             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11231             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11232             total++;
11233         }
11234     }
11235     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11236     return total;
11237 }
11238
11239 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11240 {
11241     int sq = fromX + (fromY<<4);
11242     int piece = quickBoard[sq];
11243     quickBoard[sq] = 0;
11244     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11245     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11246         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11247         moveDatabase[movePtr++].piece = Q_WCASTL;
11248         quickBoard[sq] = piece;
11249         piece = quickBoard[from]; quickBoard[from] = 0;
11250         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11251     } else
11252     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11253         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11254         moveDatabase[movePtr++].piece = Q_BCASTL;
11255         quickBoard[sq] = piece;
11256         piece = quickBoard[from]; quickBoard[from] = 0;
11257         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11258     } else
11259     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11260         quickBoard[(fromY<<4)+toX] = 0;
11261         moveDatabase[movePtr].piece = Q_EP;
11262         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11263         moveDatabase[movePtr].to = sq;
11264     } else
11265     if(promoPiece != pieceType[piece]) {
11266         moveDatabase[movePtr++].piece = Q_PROMO;
11267         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11268     }
11269     moveDatabase[movePtr].piece = piece;
11270     quickBoard[sq] = piece;
11271     movePtr++;
11272 }
11273
11274 int PackGame(Board board)
11275 {
11276     Move *newSpace = NULL;
11277     moveDatabase[movePtr].piece = 0; // terminate previous game
11278     if(movePtr > dataSize) {
11279         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11280         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11281         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11282         if(newSpace) {
11283             int i;
11284             Move *p = moveDatabase, *q = newSpace;
11285             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11286             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11287             moveDatabase = newSpace;
11288         } else { // calloc failed, we must be out of memory. Too bad...
11289             dataSize = 0; // prevent calloc events for all subsequent games
11290             return 0;     // and signal this one isn't cached
11291         }
11292     }
11293     movePtr++;
11294     MakePieceList(board, counts);
11295     return movePtr;
11296 }
11297
11298 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11299 {   // compare according to search mode
11300     int r, f;
11301     switch(appData.searchMode)
11302     {
11303       case 1: // exact position match
11304         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11305         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11306             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11307         }
11308         break;
11309       case 2: // can have extra material on empty squares
11310         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11311             if(board[r][f] == EmptySquare) continue;
11312             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11313         }
11314         break;
11315       case 3: // material with exact Pawn structure
11316         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11317             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11318             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11319         } // fall through to material comparison
11320       case 4: // exact material
11321         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11322         break;
11323       case 6: // material range with given imbalance
11324         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11325         // fall through to range comparison
11326       case 5: // material range
11327         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11328     }
11329     return TRUE;
11330 }
11331
11332 int QuickScan(Board board, Move *move)
11333 {   // reconstruct game,and compare all positions in it
11334     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11335     do {
11336         int piece = move->piece;
11337         int to = move->to, from = pieceList[piece];
11338         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11339           if(!piece) return -1;
11340           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11341             piece = (++move)->piece;
11342             from = pieceList[piece];
11343             counts[pieceType[piece]]--;
11344             pieceType[piece] = (ChessSquare) move->to;
11345             counts[move->to]++;
11346           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11347             counts[pieceType[quickBoard[to]]]--;
11348             quickBoard[to] = 0; total--;
11349             move++;
11350             continue;
11351           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11352             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11353             from  = pieceList[piece]; // so this must be King
11354             quickBoard[from] = 0;
11355             quickBoard[to] = piece;
11356             pieceList[piece] = to;
11357             move++;
11358             continue;
11359           }
11360         }
11361         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11362         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11363         quickBoard[from] = 0;
11364         quickBoard[to] = piece;
11365         pieceList[piece] = to;
11366         cnt++; turn ^= 3;
11367         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11368            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11369            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11370                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11371           ) {
11372             static int lastCounts[EmptySquare+1];
11373             int i;
11374             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11375             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11376         } else stretch = 0;
11377         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11378         move++;
11379     } while(1);
11380 }
11381
11382 void InitSearch()
11383 {
11384     int r, f;
11385     flipSearch = FALSE;
11386     CopyBoard(soughtBoard, boards[currentMove]);
11387     soughtTotal = MakePieceList(soughtBoard, maxSought);
11388     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11389     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11390     CopyBoard(reverseBoard, boards[currentMove]);
11391     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11392         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11393         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11394         reverseBoard[r][f] = piece;
11395     }
11396     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11397     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11398     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11399                  || (boards[currentMove][CASTLING][2] == NoRights || 
11400                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11401                  && (boards[currentMove][CASTLING][5] == NoRights || 
11402                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11403       ) {
11404         flipSearch = TRUE;
11405         CopyBoard(flipBoard, soughtBoard);
11406         CopyBoard(rotateBoard, reverseBoard);
11407         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11408             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11409             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11410         }
11411     }
11412     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11413     if(appData.searchMode >= 5) {
11414         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11415         MakePieceList(soughtBoard, minSought);
11416         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11417     }
11418     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11419         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11420 }
11421
11422 GameInfo dummyInfo;
11423
11424 int GameContainsPosition(FILE *f, ListGame *lg)
11425 {
11426     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11427     int fromX, fromY, toX, toY;
11428     char promoChar;
11429     static int initDone=FALSE;
11430
11431     // weed out games based on numerical tag comparison
11432     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11433     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11434     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11435     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11436     if(!initDone) {
11437         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11438         initDone = TRUE;
11439     }
11440     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11441     else CopyBoard(boards[scratch], initialPosition); // default start position
11442     if(lg->moves) {
11443         turn = btm + 1;
11444         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11445         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11446     }
11447     if(btm) plyNr++;
11448     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11449     fseek(f, lg->offset, 0);
11450     yynewfile(f);
11451     while(1) {
11452         yyboardindex = scratch;
11453         quickFlag = plyNr+1;
11454         next = Myylex();
11455         quickFlag = 0;
11456         switch(next) {
11457             case PGNTag:
11458                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11459             default:
11460                 continue;
11461
11462             case XBoardGame:
11463             case GNUChessGame:
11464                 if(plyNr) return -1; // after we have seen moves, this is for new game
11465               continue;
11466
11467             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11468             case ImpossibleMove:
11469             case WhiteWins: // game ends here with these four
11470             case BlackWins:
11471             case GameIsDrawn:
11472             case GameUnfinished:
11473                 return -1;
11474
11475             case IllegalMove:
11476                 if(appData.testLegality) return -1;
11477             case WhiteCapturesEnPassant:
11478             case BlackCapturesEnPassant:
11479             case WhitePromotion:
11480             case BlackPromotion:
11481             case WhiteNonPromotion:
11482             case BlackNonPromotion:
11483             case NormalMove:
11484             case WhiteKingSideCastle:
11485             case WhiteQueenSideCastle:
11486             case BlackKingSideCastle:
11487             case BlackQueenSideCastle:
11488             case WhiteKingSideCastleWild:
11489             case WhiteQueenSideCastleWild:
11490             case BlackKingSideCastleWild:
11491             case BlackQueenSideCastleWild:
11492             case WhiteHSideCastleFR:
11493             case WhiteASideCastleFR:
11494             case BlackHSideCastleFR:
11495             case BlackASideCastleFR:
11496                 fromX = currentMoveString[0] - AAA;
11497                 fromY = currentMoveString[1] - ONE;
11498                 toX = currentMoveString[2] - AAA;
11499                 toY = currentMoveString[3] - ONE;
11500                 promoChar = currentMoveString[4];
11501                 break;
11502             case WhiteDrop:
11503             case BlackDrop:
11504                 fromX = next == WhiteDrop ?
11505                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11506                   (int) CharToPiece(ToLower(currentMoveString[0]));
11507                 fromY = DROP_RANK;
11508                 toX = currentMoveString[2] - AAA;
11509                 toY = currentMoveString[3] - ONE;
11510                 promoChar = 0;
11511                 break;
11512         }
11513         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11514         plyNr++;
11515         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11516         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11517         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11518         if(appData.findMirror) {
11519             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11520             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11521         }
11522     }
11523 }
11524
11525 /* Load the nth game from open file f */
11526 int
11527 LoadGame(f, gameNumber, title, useList)
11528      FILE *f;
11529      int gameNumber;
11530      char *title;
11531      int useList;
11532 {
11533     ChessMove cm;
11534     char buf[MSG_SIZ];
11535     int gn = gameNumber;
11536     ListGame *lg = NULL;
11537     int numPGNTags = 0;
11538     int err, pos = -1;
11539     GameMode oldGameMode;
11540     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11541
11542     if (appData.debugMode)
11543         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11544
11545     if (gameMode == Training )
11546         SetTrainingModeOff();
11547
11548     oldGameMode = gameMode;
11549     if (gameMode != BeginningOfGame) {
11550       Reset(FALSE, TRUE);
11551     }
11552
11553     gameFileFP = f;
11554     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11555         fclose(lastLoadGameFP);
11556     }
11557
11558     if (useList) {
11559         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11560
11561         if (lg) {
11562             fseek(f, lg->offset, 0);
11563             GameListHighlight(gameNumber);
11564             pos = lg->position;
11565             gn = 1;
11566         }
11567         else {
11568             DisplayError(_("Game number out of range"), 0);
11569             return FALSE;
11570         }
11571     } else {
11572         GameListDestroy();
11573         if (fseek(f, 0, 0) == -1) {
11574             if (f == lastLoadGameFP ?
11575                 gameNumber == lastLoadGameNumber + 1 :
11576                 gameNumber == 1) {
11577                 gn = 1;
11578             } else {
11579                 DisplayError(_("Can't seek on game file"), 0);
11580                 return FALSE;
11581             }
11582         }
11583     }
11584     lastLoadGameFP = f;
11585     lastLoadGameNumber = gameNumber;
11586     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11587     lastLoadGameUseList = useList;
11588
11589     yynewfile(f);
11590
11591     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11592       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11593                 lg->gameInfo.black);
11594             DisplayTitle(buf);
11595     } else if (*title != NULLCHAR) {
11596         if (gameNumber > 1) {
11597           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11598             DisplayTitle(buf);
11599         } else {
11600             DisplayTitle(title);
11601         }
11602     }
11603
11604     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11605         gameMode = PlayFromGameFile;
11606         ModeHighlight();
11607     }
11608
11609     currentMove = forwardMostMove = backwardMostMove = 0;
11610     CopyBoard(boards[0], initialPosition);
11611     StopClocks();
11612
11613     /*
11614      * Skip the first gn-1 games in the file.
11615      * Also skip over anything that precedes an identifiable
11616      * start of game marker, to avoid being confused by
11617      * garbage at the start of the file.  Currently
11618      * recognized start of game markers are the move number "1",
11619      * the pattern "gnuchess .* game", the pattern
11620      * "^[#;%] [^ ]* game file", and a PGN tag block.
11621      * A game that starts with one of the latter two patterns
11622      * will also have a move number 1, possibly
11623      * following a position diagram.
11624      * 5-4-02: Let's try being more lenient and allowing a game to
11625      * start with an unnumbered move.  Does that break anything?
11626      */
11627     cm = lastLoadGameStart = EndOfFile;
11628     while (gn > 0) {
11629         yyboardindex = forwardMostMove;
11630         cm = (ChessMove) Myylex();
11631         switch (cm) {
11632           case EndOfFile:
11633             if (cmailMsgLoaded) {
11634                 nCmailGames = CMAIL_MAX_GAMES - gn;
11635             } else {
11636                 Reset(TRUE, TRUE);
11637                 DisplayError(_("Game not found in file"), 0);
11638             }
11639             return FALSE;
11640
11641           case GNUChessGame:
11642           case XBoardGame:
11643             gn--;
11644             lastLoadGameStart = cm;
11645             break;
11646
11647           case MoveNumberOne:
11648             switch (lastLoadGameStart) {
11649               case GNUChessGame:
11650               case XBoardGame:
11651               case PGNTag:
11652                 break;
11653               case MoveNumberOne:
11654               case EndOfFile:
11655                 gn--;           /* count this game */
11656                 lastLoadGameStart = cm;
11657                 break;
11658               default:
11659                 /* impossible */
11660                 break;
11661             }
11662             break;
11663
11664           case PGNTag:
11665             switch (lastLoadGameStart) {
11666               case GNUChessGame:
11667               case PGNTag:
11668               case MoveNumberOne:
11669               case EndOfFile:
11670                 gn--;           /* count this game */
11671                 lastLoadGameStart = cm;
11672                 break;
11673               case XBoardGame:
11674                 lastLoadGameStart = cm; /* game counted already */
11675                 break;
11676               default:
11677                 /* impossible */
11678                 break;
11679             }
11680             if (gn > 0) {
11681                 do {
11682                     yyboardindex = forwardMostMove;
11683                     cm = (ChessMove) Myylex();
11684                 } while (cm == PGNTag || cm == Comment);
11685             }
11686             break;
11687
11688           case WhiteWins:
11689           case BlackWins:
11690           case GameIsDrawn:
11691             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11692                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11693                     != CMAIL_OLD_RESULT) {
11694                     nCmailResults ++ ;
11695                     cmailResult[  CMAIL_MAX_GAMES
11696                                 - gn - 1] = CMAIL_OLD_RESULT;
11697                 }
11698             }
11699             break;
11700
11701           case NormalMove:
11702             /* Only a NormalMove can be at the start of a game
11703              * without a position diagram. */
11704             if (lastLoadGameStart == EndOfFile ) {
11705               gn--;
11706               lastLoadGameStart = MoveNumberOne;
11707             }
11708             break;
11709
11710           default:
11711             break;
11712         }
11713     }
11714
11715     if (appData.debugMode)
11716       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11717
11718     if (cm == XBoardGame) {
11719         /* Skip any header junk before position diagram and/or move 1 */
11720         for (;;) {
11721             yyboardindex = forwardMostMove;
11722             cm = (ChessMove) Myylex();
11723
11724             if (cm == EndOfFile ||
11725                 cm == GNUChessGame || cm == XBoardGame) {
11726                 /* Empty game; pretend end-of-file and handle later */
11727                 cm = EndOfFile;
11728                 break;
11729             }
11730
11731             if (cm == MoveNumberOne || cm == PositionDiagram ||
11732                 cm == PGNTag || cm == Comment)
11733               break;
11734         }
11735     } else if (cm == GNUChessGame) {
11736         if (gameInfo.event != NULL) {
11737             free(gameInfo.event);
11738         }
11739         gameInfo.event = StrSave(yy_text);
11740     }
11741
11742     startedFromSetupPosition = FALSE;
11743     while (cm == PGNTag) {
11744         if (appData.debugMode)
11745           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11746         err = ParsePGNTag(yy_text, &gameInfo);
11747         if (!err) numPGNTags++;
11748
11749         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11750         if(gameInfo.variant != oldVariant) {
11751             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11752             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11753             InitPosition(TRUE);
11754             oldVariant = gameInfo.variant;
11755             if (appData.debugMode)
11756               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11757         }
11758
11759
11760         if (gameInfo.fen != NULL) {
11761           Board initial_position;
11762           startedFromSetupPosition = TRUE;
11763           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11764             Reset(TRUE, TRUE);
11765             DisplayError(_("Bad FEN position in file"), 0);
11766             return FALSE;
11767           }
11768           CopyBoard(boards[0], initial_position);
11769           if (blackPlaysFirst) {
11770             currentMove = forwardMostMove = backwardMostMove = 1;
11771             CopyBoard(boards[1], initial_position);
11772             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11773             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11774             timeRemaining[0][1] = whiteTimeRemaining;
11775             timeRemaining[1][1] = blackTimeRemaining;
11776             if (commentList[0] != NULL) {
11777               commentList[1] = commentList[0];
11778               commentList[0] = NULL;
11779             }
11780           } else {
11781             currentMove = forwardMostMove = backwardMostMove = 0;
11782           }
11783           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11784           {   int i;
11785               initialRulePlies = FENrulePlies;
11786               for( i=0; i< nrCastlingRights; i++ )
11787                   initialRights[i] = initial_position[CASTLING][i];
11788           }
11789           yyboardindex = forwardMostMove;
11790           free(gameInfo.fen);
11791           gameInfo.fen = NULL;
11792         }
11793
11794         yyboardindex = forwardMostMove;
11795         cm = (ChessMove) Myylex();
11796
11797         /* Handle comments interspersed among the tags */
11798         while (cm == Comment) {
11799             char *p;
11800             if (appData.debugMode)
11801               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11802             p = yy_text;
11803             AppendComment(currentMove, p, FALSE);
11804             yyboardindex = forwardMostMove;
11805             cm = (ChessMove) Myylex();
11806         }
11807     }
11808
11809     /* don't rely on existence of Event tag since if game was
11810      * pasted from clipboard the Event tag may not exist
11811      */
11812     if (numPGNTags > 0){
11813         char *tags;
11814         if (gameInfo.variant == VariantNormal) {
11815           VariantClass v = StringToVariant(gameInfo.event);
11816           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11817           if(v < VariantShogi) gameInfo.variant = v;
11818         }
11819         if (!matchMode) {
11820           if( appData.autoDisplayTags ) {
11821             tags = PGNTags(&gameInfo);
11822             TagsPopUp(tags, CmailMsg());
11823             free(tags);
11824           }
11825         }
11826     } else {
11827         /* Make something up, but don't display it now */
11828         SetGameInfo();
11829         TagsPopDown();
11830     }
11831
11832     if (cm == PositionDiagram) {
11833         int i, j;
11834         char *p;
11835         Board initial_position;
11836
11837         if (appData.debugMode)
11838           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11839
11840         if (!startedFromSetupPosition) {
11841             p = yy_text;
11842             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11843               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11844                 switch (*p) {
11845                   case '{':
11846                   case '[':
11847                   case '-':
11848                   case ' ':
11849                   case '\t':
11850                   case '\n':
11851                   case '\r':
11852                     break;
11853                   default:
11854                     initial_position[i][j++] = CharToPiece(*p);
11855                     break;
11856                 }
11857             while (*p == ' ' || *p == '\t' ||
11858                    *p == '\n' || *p == '\r') p++;
11859
11860             if (strncmp(p, "black", strlen("black"))==0)
11861               blackPlaysFirst = TRUE;
11862             else
11863               blackPlaysFirst = FALSE;
11864             startedFromSetupPosition = TRUE;
11865
11866             CopyBoard(boards[0], initial_position);
11867             if (blackPlaysFirst) {
11868                 currentMove = forwardMostMove = backwardMostMove = 1;
11869                 CopyBoard(boards[1], initial_position);
11870                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11871                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11872                 timeRemaining[0][1] = whiteTimeRemaining;
11873                 timeRemaining[1][1] = blackTimeRemaining;
11874                 if (commentList[0] != NULL) {
11875                     commentList[1] = commentList[0];
11876                     commentList[0] = NULL;
11877                 }
11878             } else {
11879                 currentMove = forwardMostMove = backwardMostMove = 0;
11880             }
11881         }
11882         yyboardindex = forwardMostMove;
11883         cm = (ChessMove) Myylex();
11884     }
11885
11886     if (first.pr == NoProc) {
11887         StartChessProgram(&first);
11888     }
11889     InitChessProgram(&first, FALSE);
11890     SendToProgram("force\n", &first);
11891     if (startedFromSetupPosition) {
11892         SendBoard(&first, forwardMostMove);
11893     if (appData.debugMode) {
11894         fprintf(debugFP, "Load Game\n");
11895     }
11896         DisplayBothClocks();
11897     }
11898
11899     /* [HGM] server: flag to write setup moves in broadcast file as one */
11900     loadFlag = appData.suppressLoadMoves;
11901
11902     while (cm == Comment) {
11903         char *p;
11904         if (appData.debugMode)
11905           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11906         p = yy_text;
11907         AppendComment(currentMove, p, FALSE);
11908         yyboardindex = forwardMostMove;
11909         cm = (ChessMove) Myylex();
11910     }
11911
11912     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11913         cm == WhiteWins || cm == BlackWins ||
11914         cm == GameIsDrawn || cm == GameUnfinished) {
11915         DisplayMessage("", _("No moves in game"));
11916         if (cmailMsgLoaded) {
11917             if (appData.debugMode)
11918               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11919             ClearHighlights();
11920             flipView = FALSE;
11921         }
11922         DrawPosition(FALSE, boards[currentMove]);
11923         DisplayBothClocks();
11924         gameMode = EditGame;
11925         ModeHighlight();
11926         gameFileFP = NULL;
11927         cmailOldMove = 0;
11928         return TRUE;
11929     }
11930
11931     // [HGM] PV info: routine tests if comment empty
11932     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11933         DisplayComment(currentMove - 1, commentList[currentMove]);
11934     }
11935     if (!matchMode && appData.timeDelay != 0)
11936       DrawPosition(FALSE, boards[currentMove]);
11937
11938     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11939       programStats.ok_to_send = 1;
11940     }
11941
11942     /* if the first token after the PGN tags is a move
11943      * and not move number 1, retrieve it from the parser
11944      */
11945     if (cm != MoveNumberOne)
11946         LoadGameOneMove(cm);
11947
11948     /* load the remaining moves from the file */
11949     while (LoadGameOneMove(EndOfFile)) {
11950       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11951       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11952     }
11953
11954     /* rewind to the start of the game */
11955     currentMove = backwardMostMove;
11956
11957     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11958
11959     if (oldGameMode == AnalyzeFile ||
11960         oldGameMode == AnalyzeMode) {
11961       AnalyzeFileEvent();
11962     }
11963
11964     if (!matchMode && pos >= 0) {
11965         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11966     } else
11967     if (matchMode || appData.timeDelay == 0) {
11968       ToEndEvent();
11969     } else if (appData.timeDelay > 0) {
11970       AutoPlayGameLoop();
11971     }
11972
11973     if (appData.debugMode)
11974         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11975
11976     loadFlag = 0; /* [HGM] true game starts */
11977     return TRUE;
11978 }
11979
11980 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11981 int
11982 ReloadPosition(offset)
11983      int offset;
11984 {
11985     int positionNumber = lastLoadPositionNumber + offset;
11986     if (lastLoadPositionFP == NULL) {
11987         DisplayError(_("No position has been loaded yet"), 0);
11988         return FALSE;
11989     }
11990     if (positionNumber <= 0) {
11991         DisplayError(_("Can't back up any further"), 0);
11992         return FALSE;
11993     }
11994     return LoadPosition(lastLoadPositionFP, positionNumber,
11995                         lastLoadPositionTitle);
11996 }
11997
11998 /* Load the nth position from the given file */
11999 int
12000 LoadPositionFromFile(filename, n, title)
12001      char *filename;
12002      int n;
12003      char *title;
12004 {
12005     FILE *f;
12006     char buf[MSG_SIZ];
12007
12008     if (strcmp(filename, "-") == 0) {
12009         return LoadPosition(stdin, n, "stdin");
12010     } else {
12011         f = fopen(filename, "rb");
12012         if (f == NULL) {
12013             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12014             DisplayError(buf, errno);
12015             return FALSE;
12016         } else {
12017             return LoadPosition(f, n, title);
12018         }
12019     }
12020 }
12021
12022 /* Load the nth position from the given open file, and close it */
12023 int
12024 LoadPosition(f, positionNumber, title)
12025      FILE *f;
12026      int positionNumber;
12027      char *title;
12028 {
12029     char *p, line[MSG_SIZ];
12030     Board initial_position;
12031     int i, j, fenMode, pn;
12032
12033     if (gameMode == Training )
12034         SetTrainingModeOff();
12035
12036     if (gameMode != BeginningOfGame) {
12037         Reset(FALSE, TRUE);
12038     }
12039     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12040         fclose(lastLoadPositionFP);
12041     }
12042     if (positionNumber == 0) positionNumber = 1;
12043     lastLoadPositionFP = f;
12044     lastLoadPositionNumber = positionNumber;
12045     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12046     if (first.pr == NoProc && !appData.noChessProgram) {
12047       StartChessProgram(&first);
12048       InitChessProgram(&first, FALSE);
12049     }
12050     pn = positionNumber;
12051     if (positionNumber < 0) {
12052         /* Negative position number means to seek to that byte offset */
12053         if (fseek(f, -positionNumber, 0) == -1) {
12054             DisplayError(_("Can't seek on position file"), 0);
12055             return FALSE;
12056         };
12057         pn = 1;
12058     } else {
12059         if (fseek(f, 0, 0) == -1) {
12060             if (f == lastLoadPositionFP ?
12061                 positionNumber == lastLoadPositionNumber + 1 :
12062                 positionNumber == 1) {
12063                 pn = 1;
12064             } else {
12065                 DisplayError(_("Can't seek on position file"), 0);
12066                 return FALSE;
12067             }
12068         }
12069     }
12070     /* See if this file is FEN or old-style xboard */
12071     if (fgets(line, MSG_SIZ, f) == NULL) {
12072         DisplayError(_("Position not found in file"), 0);
12073         return FALSE;
12074     }
12075     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12076     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12077
12078     if (pn >= 2) {
12079         if (fenMode || line[0] == '#') pn--;
12080         while (pn > 0) {
12081             /* skip positions before number pn */
12082             if (fgets(line, MSG_SIZ, f) == NULL) {
12083                 Reset(TRUE, TRUE);
12084                 DisplayError(_("Position not found in file"), 0);
12085                 return FALSE;
12086             }
12087             if (fenMode || line[0] == '#') pn--;
12088         }
12089     }
12090
12091     if (fenMode) {
12092         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12093             DisplayError(_("Bad FEN position in file"), 0);
12094             return FALSE;
12095         }
12096     } else {
12097         (void) fgets(line, MSG_SIZ, f);
12098         (void) fgets(line, MSG_SIZ, f);
12099
12100         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12101             (void) fgets(line, MSG_SIZ, f);
12102             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12103                 if (*p == ' ')
12104                   continue;
12105                 initial_position[i][j++] = CharToPiece(*p);
12106             }
12107         }
12108
12109         blackPlaysFirst = FALSE;
12110         if (!feof(f)) {
12111             (void) fgets(line, MSG_SIZ, f);
12112             if (strncmp(line, "black", strlen("black"))==0)
12113               blackPlaysFirst = TRUE;
12114         }
12115     }
12116     startedFromSetupPosition = TRUE;
12117
12118     CopyBoard(boards[0], initial_position);
12119     if (blackPlaysFirst) {
12120         currentMove = forwardMostMove = backwardMostMove = 1;
12121         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12122         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12123         CopyBoard(boards[1], initial_position);
12124         DisplayMessage("", _("Black to play"));
12125     } else {
12126         currentMove = forwardMostMove = backwardMostMove = 0;
12127         DisplayMessage("", _("White to play"));
12128     }
12129     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12130     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12131         SendToProgram("force\n", &first);
12132         SendBoard(&first, forwardMostMove);
12133     }
12134     if (appData.debugMode) {
12135 int i, j;
12136   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12137   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12138         fprintf(debugFP, "Load Position\n");
12139     }
12140
12141     if (positionNumber > 1) {
12142       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12143         DisplayTitle(line);
12144     } else {
12145         DisplayTitle(title);
12146     }
12147     gameMode = EditGame;
12148     ModeHighlight();
12149     ResetClocks();
12150     timeRemaining[0][1] = whiteTimeRemaining;
12151     timeRemaining[1][1] = blackTimeRemaining;
12152     DrawPosition(FALSE, boards[currentMove]);
12153
12154     return TRUE;
12155 }
12156
12157
12158 void
12159 CopyPlayerNameIntoFileName(dest, src)
12160      char **dest, *src;
12161 {
12162     while (*src != NULLCHAR && *src != ',') {
12163         if (*src == ' ') {
12164             *(*dest)++ = '_';
12165             src++;
12166         } else {
12167             *(*dest)++ = *src++;
12168         }
12169     }
12170 }
12171
12172 char *DefaultFileName(ext)
12173      char *ext;
12174 {
12175     static char def[MSG_SIZ];
12176     char *p;
12177
12178     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12179         p = def;
12180         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12181         *p++ = '-';
12182         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12183         *p++ = '.';
12184         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12185     } else {
12186         def[0] = NULLCHAR;
12187     }
12188     return def;
12189 }
12190
12191 /* Save the current game to the given file */
12192 int
12193 SaveGameToFile(filename, append)
12194      char *filename;
12195      int append;
12196 {
12197     FILE *f;
12198     char buf[MSG_SIZ];
12199     int result, i, t,tot=0;
12200
12201     if (strcmp(filename, "-") == 0) {
12202         return SaveGame(stdout, 0, NULL);
12203     } else {
12204         for(i=0; i<10; i++) { // upto 10 tries
12205              f = fopen(filename, append ? "a" : "w");
12206              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12207              if(f || errno != 13) break;
12208              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12209              tot += t;
12210         }
12211         if (f == NULL) {
12212             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12213             DisplayError(buf, errno);
12214             return FALSE;
12215         } else {
12216             safeStrCpy(buf, lastMsg, MSG_SIZ);
12217             DisplayMessage(_("Waiting for access to save file"), "");
12218             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12219             DisplayMessage(_("Saving game"), "");
12220             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12221             result = SaveGame(f, 0, NULL);
12222             DisplayMessage(buf, "");
12223             return result;
12224         }
12225     }
12226 }
12227
12228 char *
12229 SavePart(str)
12230      char *str;
12231 {
12232     static char buf[MSG_SIZ];
12233     char *p;
12234
12235     p = strchr(str, ' ');
12236     if (p == NULL) return str;
12237     strncpy(buf, str, p - str);
12238     buf[p - str] = NULLCHAR;
12239     return buf;
12240 }
12241
12242 #define PGN_MAX_LINE 75
12243
12244 #define PGN_SIDE_WHITE  0
12245 #define PGN_SIDE_BLACK  1
12246
12247 /* [AS] */
12248 static int FindFirstMoveOutOfBook( int side )
12249 {
12250     int result = -1;
12251
12252     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12253         int index = backwardMostMove;
12254         int has_book_hit = 0;
12255
12256         if( (index % 2) != side ) {
12257             index++;
12258         }
12259
12260         while( index < forwardMostMove ) {
12261             /* Check to see if engine is in book */
12262             int depth = pvInfoList[index].depth;
12263             int score = pvInfoList[index].score;
12264             int in_book = 0;
12265
12266             if( depth <= 2 ) {
12267                 in_book = 1;
12268             }
12269             else if( score == 0 && depth == 63 ) {
12270                 in_book = 1; /* Zappa */
12271             }
12272             else if( score == 2 && depth == 99 ) {
12273                 in_book = 1; /* Abrok */
12274             }
12275
12276             has_book_hit += in_book;
12277
12278             if( ! in_book ) {
12279                 result = index;
12280
12281                 break;
12282             }
12283
12284             index += 2;
12285         }
12286     }
12287
12288     return result;
12289 }
12290
12291 /* [AS] */
12292 void GetOutOfBookInfo( char * buf )
12293 {
12294     int oob[2];
12295     int i;
12296     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12297
12298     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12299     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12300
12301     *buf = '\0';
12302
12303     if( oob[0] >= 0 || oob[1] >= 0 ) {
12304         for( i=0; i<2; i++ ) {
12305             int idx = oob[i];
12306
12307             if( idx >= 0 ) {
12308                 if( i > 0 && oob[0] >= 0 ) {
12309                     strcat( buf, "   " );
12310                 }
12311
12312                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12313                 sprintf( buf+strlen(buf), "%s%.2f",
12314                     pvInfoList[idx].score >= 0 ? "+" : "",
12315                     pvInfoList[idx].score / 100.0 );
12316             }
12317         }
12318     }
12319 }
12320
12321 /* Save game in PGN style and close the file */
12322 int
12323 SaveGamePGN(f)
12324      FILE *f;
12325 {
12326     int i, offset, linelen, newblock;
12327     time_t tm;
12328 //    char *movetext;
12329     char numtext[32];
12330     int movelen, numlen, blank;
12331     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12332
12333     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12334
12335     tm = time((time_t *) NULL);
12336
12337     PrintPGNTags(f, &gameInfo);
12338
12339     if (backwardMostMove > 0 || startedFromSetupPosition) {
12340         char *fen = PositionToFEN(backwardMostMove, NULL);
12341         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12342         fprintf(f, "\n{--------------\n");
12343         PrintPosition(f, backwardMostMove);
12344         fprintf(f, "--------------}\n");
12345         free(fen);
12346     }
12347     else {
12348         /* [AS] Out of book annotation */
12349         if( appData.saveOutOfBookInfo ) {
12350             char buf[64];
12351
12352             GetOutOfBookInfo( buf );
12353
12354             if( buf[0] != '\0' ) {
12355                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12356             }
12357         }
12358
12359         fprintf(f, "\n");
12360     }
12361
12362     i = backwardMostMove;
12363     linelen = 0;
12364     newblock = TRUE;
12365
12366     while (i < forwardMostMove) {
12367         /* Print comments preceding this move */
12368         if (commentList[i] != NULL) {
12369             if (linelen > 0) fprintf(f, "\n");
12370             fprintf(f, "%s", commentList[i]);
12371             linelen = 0;
12372             newblock = TRUE;
12373         }
12374
12375         /* Format move number */
12376         if ((i % 2) == 0)
12377           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12378         else
12379           if (newblock)
12380             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12381           else
12382             numtext[0] = NULLCHAR;
12383
12384         numlen = strlen(numtext);
12385         newblock = FALSE;
12386
12387         /* Print move number */
12388         blank = linelen > 0 && numlen > 0;
12389         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12390             fprintf(f, "\n");
12391             linelen = 0;
12392             blank = 0;
12393         }
12394         if (blank) {
12395             fprintf(f, " ");
12396             linelen++;
12397         }
12398         fprintf(f, "%s", numtext);
12399         linelen += numlen;
12400
12401         /* Get move */
12402         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12403         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12404
12405         /* Print move */
12406         blank = linelen > 0 && movelen > 0;
12407         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12408             fprintf(f, "\n");
12409             linelen = 0;
12410             blank = 0;
12411         }
12412         if (blank) {
12413             fprintf(f, " ");
12414             linelen++;
12415         }
12416         fprintf(f, "%s", move_buffer);
12417         linelen += movelen;
12418
12419         /* [AS] Add PV info if present */
12420         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12421             /* [HGM] add time */
12422             char buf[MSG_SIZ]; int seconds;
12423
12424             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12425
12426             if( seconds <= 0)
12427               buf[0] = 0;
12428             else
12429               if( seconds < 30 )
12430                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12431               else
12432                 {
12433                   seconds = (seconds + 4)/10; // round to full seconds
12434                   if( seconds < 60 )
12435                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12436                   else
12437                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12438                 }
12439
12440             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12441                       pvInfoList[i].score >= 0 ? "+" : "",
12442                       pvInfoList[i].score / 100.0,
12443                       pvInfoList[i].depth,
12444                       buf );
12445
12446             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12447
12448             /* Print score/depth */
12449             blank = linelen > 0 && movelen > 0;
12450             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12451                 fprintf(f, "\n");
12452                 linelen = 0;
12453                 blank = 0;
12454             }
12455             if (blank) {
12456                 fprintf(f, " ");
12457                 linelen++;
12458             }
12459             fprintf(f, "%s", move_buffer);
12460             linelen += movelen;
12461         }
12462
12463         i++;
12464     }
12465
12466     /* Start a new line */
12467     if (linelen > 0) fprintf(f, "\n");
12468
12469     /* Print comments after last move */
12470     if (commentList[i] != NULL) {
12471         fprintf(f, "%s\n", commentList[i]);
12472     }
12473
12474     /* Print result */
12475     if (gameInfo.resultDetails != NULL &&
12476         gameInfo.resultDetails[0] != NULLCHAR) {
12477         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12478                 PGNResult(gameInfo.result));
12479     } else {
12480         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12481     }
12482
12483     fclose(f);
12484     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12485     return TRUE;
12486 }
12487
12488 /* Save game in old style and close the file */
12489 int
12490 SaveGameOldStyle(f)
12491      FILE *f;
12492 {
12493     int i, offset;
12494     time_t tm;
12495
12496     tm = time((time_t *) NULL);
12497
12498     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12499     PrintOpponents(f);
12500
12501     if (backwardMostMove > 0 || startedFromSetupPosition) {
12502         fprintf(f, "\n[--------------\n");
12503         PrintPosition(f, backwardMostMove);
12504         fprintf(f, "--------------]\n");
12505     } else {
12506         fprintf(f, "\n");
12507     }
12508
12509     i = backwardMostMove;
12510     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12511
12512     while (i < forwardMostMove) {
12513         if (commentList[i] != NULL) {
12514             fprintf(f, "[%s]\n", commentList[i]);
12515         }
12516
12517         if ((i % 2) == 1) {
12518             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12519             i++;
12520         } else {
12521             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12522             i++;
12523             if (commentList[i] != NULL) {
12524                 fprintf(f, "\n");
12525                 continue;
12526             }
12527             if (i >= forwardMostMove) {
12528                 fprintf(f, "\n");
12529                 break;
12530             }
12531             fprintf(f, "%s\n", parseList[i]);
12532             i++;
12533         }
12534     }
12535
12536     if (commentList[i] != NULL) {
12537         fprintf(f, "[%s]\n", commentList[i]);
12538     }
12539
12540     /* This isn't really the old style, but it's close enough */
12541     if (gameInfo.resultDetails != NULL &&
12542         gameInfo.resultDetails[0] != NULLCHAR) {
12543         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12544                 gameInfo.resultDetails);
12545     } else {
12546         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12547     }
12548
12549     fclose(f);
12550     return TRUE;
12551 }
12552
12553 /* Save the current game to open file f and close the file */
12554 int
12555 SaveGame(f, dummy, dummy2)
12556      FILE *f;
12557      int dummy;
12558      char *dummy2;
12559 {
12560     if (gameMode == EditPosition) EditPositionDone(TRUE);
12561     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12562     if (appData.oldSaveStyle)
12563       return SaveGameOldStyle(f);
12564     else
12565       return SaveGamePGN(f);
12566 }
12567
12568 /* Save the current position to the given file */
12569 int
12570 SavePositionToFile(filename)
12571      char *filename;
12572 {
12573     FILE *f;
12574     char buf[MSG_SIZ];
12575
12576     if (strcmp(filename, "-") == 0) {
12577         return SavePosition(stdout, 0, NULL);
12578     } else {
12579         f = fopen(filename, "a");
12580         if (f == NULL) {
12581             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12582             DisplayError(buf, errno);
12583             return FALSE;
12584         } else {
12585             safeStrCpy(buf, lastMsg, MSG_SIZ);
12586             DisplayMessage(_("Waiting for access to save file"), "");
12587             flock(fileno(f), LOCK_EX); // [HGM] lock
12588             DisplayMessage(_("Saving position"), "");
12589             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12590             SavePosition(f, 0, NULL);
12591             DisplayMessage(buf, "");
12592             return TRUE;
12593         }
12594     }
12595 }
12596
12597 /* Save the current position to the given open file and close the file */
12598 int
12599 SavePosition(f, dummy, dummy2)
12600      FILE *f;
12601      int dummy;
12602      char *dummy2;
12603 {
12604     time_t tm;
12605     char *fen;
12606
12607     if (gameMode == EditPosition) EditPositionDone(TRUE);
12608     if (appData.oldSaveStyle) {
12609         tm = time((time_t *) NULL);
12610
12611         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12612         PrintOpponents(f);
12613         fprintf(f, "[--------------\n");
12614         PrintPosition(f, currentMove);
12615         fprintf(f, "--------------]\n");
12616     } else {
12617         fen = PositionToFEN(currentMove, NULL);
12618         fprintf(f, "%s\n", fen);
12619         free(fen);
12620     }
12621     fclose(f);
12622     return TRUE;
12623 }
12624
12625 void
12626 ReloadCmailMsgEvent(unregister)
12627      int unregister;
12628 {
12629 #if !WIN32
12630     static char *inFilename = NULL;
12631     static char *outFilename;
12632     int i;
12633     struct stat inbuf, outbuf;
12634     int status;
12635
12636     /* Any registered moves are unregistered if unregister is set, */
12637     /* i.e. invoked by the signal handler */
12638     if (unregister) {
12639         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12640             cmailMoveRegistered[i] = FALSE;
12641             if (cmailCommentList[i] != NULL) {
12642                 free(cmailCommentList[i]);
12643                 cmailCommentList[i] = NULL;
12644             }
12645         }
12646         nCmailMovesRegistered = 0;
12647     }
12648
12649     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12650         cmailResult[i] = CMAIL_NOT_RESULT;
12651     }
12652     nCmailResults = 0;
12653
12654     if (inFilename == NULL) {
12655         /* Because the filenames are static they only get malloced once  */
12656         /* and they never get freed                                      */
12657         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12658         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12659
12660         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12661         sprintf(outFilename, "%s.out", appData.cmailGameName);
12662     }
12663
12664     status = stat(outFilename, &outbuf);
12665     if (status < 0) {
12666         cmailMailedMove = FALSE;
12667     } else {
12668         status = stat(inFilename, &inbuf);
12669         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12670     }
12671
12672     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12673        counts the games, notes how each one terminated, etc.
12674
12675        It would be nice to remove this kludge and instead gather all
12676        the information while building the game list.  (And to keep it
12677        in the game list nodes instead of having a bunch of fixed-size
12678        parallel arrays.)  Note this will require getting each game's
12679        termination from the PGN tags, as the game list builder does
12680        not process the game moves.  --mann
12681        */
12682     cmailMsgLoaded = TRUE;
12683     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12684
12685     /* Load first game in the file or popup game menu */
12686     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12687
12688 #endif /* !WIN32 */
12689     return;
12690 }
12691
12692 int
12693 RegisterMove()
12694 {
12695     FILE *f;
12696     char string[MSG_SIZ];
12697
12698     if (   cmailMailedMove
12699         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12700         return TRUE;            /* Allow free viewing  */
12701     }
12702
12703     /* Unregister move to ensure that we don't leave RegisterMove        */
12704     /* with the move registered when the conditions for registering no   */
12705     /* longer hold                                                       */
12706     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12707         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12708         nCmailMovesRegistered --;
12709
12710         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12711           {
12712               free(cmailCommentList[lastLoadGameNumber - 1]);
12713               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12714           }
12715     }
12716
12717     if (cmailOldMove == -1) {
12718         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12719         return FALSE;
12720     }
12721
12722     if (currentMove > cmailOldMove + 1) {
12723         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12724         return FALSE;
12725     }
12726
12727     if (currentMove < cmailOldMove) {
12728         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12729         return FALSE;
12730     }
12731
12732     if (forwardMostMove > currentMove) {
12733         /* Silently truncate extra moves */
12734         TruncateGame();
12735     }
12736
12737     if (   (currentMove == cmailOldMove + 1)
12738         || (   (currentMove == cmailOldMove)
12739             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12740                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12741         if (gameInfo.result != GameUnfinished) {
12742             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12743         }
12744
12745         if (commentList[currentMove] != NULL) {
12746             cmailCommentList[lastLoadGameNumber - 1]
12747               = StrSave(commentList[currentMove]);
12748         }
12749         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12750
12751         if (appData.debugMode)
12752           fprintf(debugFP, "Saving %s for game %d\n",
12753                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12754
12755         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12756
12757         f = fopen(string, "w");
12758         if (appData.oldSaveStyle) {
12759             SaveGameOldStyle(f); /* also closes the file */
12760
12761             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12762             f = fopen(string, "w");
12763             SavePosition(f, 0, NULL); /* also closes the file */
12764         } else {
12765             fprintf(f, "{--------------\n");
12766             PrintPosition(f, currentMove);
12767             fprintf(f, "--------------}\n\n");
12768
12769             SaveGame(f, 0, NULL); /* also closes the file*/
12770         }
12771
12772         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12773         nCmailMovesRegistered ++;
12774     } else if (nCmailGames == 1) {
12775         DisplayError(_("You have not made a move yet"), 0);
12776         return FALSE;
12777     }
12778
12779     return TRUE;
12780 }
12781
12782 void
12783 MailMoveEvent()
12784 {
12785 #if !WIN32
12786     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12787     FILE *commandOutput;
12788     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12789     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12790     int nBuffers;
12791     int i;
12792     int archived;
12793     char *arcDir;
12794
12795     if (! cmailMsgLoaded) {
12796         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12797         return;
12798     }
12799
12800     if (nCmailGames == nCmailResults) {
12801         DisplayError(_("No unfinished games"), 0);
12802         return;
12803     }
12804
12805 #if CMAIL_PROHIBIT_REMAIL
12806     if (cmailMailedMove) {
12807       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);
12808         DisplayError(msg, 0);
12809         return;
12810     }
12811 #endif
12812
12813     if (! (cmailMailedMove || RegisterMove())) return;
12814
12815     if (   cmailMailedMove
12816         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12817       snprintf(string, MSG_SIZ, partCommandString,
12818                appData.debugMode ? " -v" : "", appData.cmailGameName);
12819         commandOutput = popen(string, "r");
12820
12821         if (commandOutput == NULL) {
12822             DisplayError(_("Failed to invoke cmail"), 0);
12823         } else {
12824             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12825                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12826             }
12827             if (nBuffers > 1) {
12828                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12829                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12830                 nBytes = MSG_SIZ - 1;
12831             } else {
12832                 (void) memcpy(msg, buffer, nBytes);
12833             }
12834             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12835
12836             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12837                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12838
12839                 archived = TRUE;
12840                 for (i = 0; i < nCmailGames; i ++) {
12841                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12842                         archived = FALSE;
12843                     }
12844                 }
12845                 if (   archived
12846                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12847                         != NULL)) {
12848                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12849                            arcDir,
12850                            appData.cmailGameName,
12851                            gameInfo.date);
12852                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12853                     cmailMsgLoaded = FALSE;
12854                 }
12855             }
12856
12857             DisplayInformation(msg);
12858             pclose(commandOutput);
12859         }
12860     } else {
12861         if ((*cmailMsg) != '\0') {
12862             DisplayInformation(cmailMsg);
12863         }
12864     }
12865
12866     return;
12867 #endif /* !WIN32 */
12868 }
12869
12870 char *
12871 CmailMsg()
12872 {
12873 #if WIN32
12874     return NULL;
12875 #else
12876     int  prependComma = 0;
12877     char number[5];
12878     char string[MSG_SIZ];       /* Space for game-list */
12879     int  i;
12880
12881     if (!cmailMsgLoaded) return "";
12882
12883     if (cmailMailedMove) {
12884       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12885     } else {
12886         /* Create a list of games left */
12887       snprintf(string, MSG_SIZ, "[");
12888         for (i = 0; i < nCmailGames; i ++) {
12889             if (! (   cmailMoveRegistered[i]
12890                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12891                 if (prependComma) {
12892                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12893                 } else {
12894                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12895                     prependComma = 1;
12896                 }
12897
12898                 strcat(string, number);
12899             }
12900         }
12901         strcat(string, "]");
12902
12903         if (nCmailMovesRegistered + nCmailResults == 0) {
12904             switch (nCmailGames) {
12905               case 1:
12906                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12907                 break;
12908
12909               case 2:
12910                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12911                 break;
12912
12913               default:
12914                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12915                          nCmailGames);
12916                 break;
12917             }
12918         } else {
12919             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12920               case 1:
12921                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12922                          string);
12923                 break;
12924
12925               case 0:
12926                 if (nCmailResults == nCmailGames) {
12927                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12928                 } else {
12929                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12930                 }
12931                 break;
12932
12933               default:
12934                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12935                          string);
12936             }
12937         }
12938     }
12939     return cmailMsg;
12940 #endif /* WIN32 */
12941 }
12942
12943 void
12944 ResetGameEvent()
12945 {
12946     if (gameMode == Training)
12947       SetTrainingModeOff();
12948
12949     Reset(TRUE, TRUE);
12950     cmailMsgLoaded = FALSE;
12951     if (appData.icsActive) {
12952       SendToICS(ics_prefix);
12953       SendToICS("refresh\n");
12954     }
12955 }
12956
12957 void
12958 ExitEvent(status)
12959      int status;
12960 {
12961     exiting++;
12962     if (exiting > 2) {
12963       /* Give up on clean exit */
12964       exit(status);
12965     }
12966     if (exiting > 1) {
12967       /* Keep trying for clean exit */
12968       return;
12969     }
12970
12971     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12972
12973     if (telnetISR != NULL) {
12974       RemoveInputSource(telnetISR);
12975     }
12976     if (icsPR != NoProc) {
12977       DestroyChildProcess(icsPR, TRUE);
12978     }
12979
12980     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12981     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12982
12983     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12984     /* make sure this other one finishes before killing it!                  */
12985     if(endingGame) { int count = 0;
12986         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12987         while(endingGame && count++ < 10) DoSleep(1);
12988         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12989     }
12990
12991     /* Kill off chess programs */
12992     if (first.pr != NoProc) {
12993         ExitAnalyzeMode();
12994
12995         DoSleep( appData.delayBeforeQuit );
12996         SendToProgram("quit\n", &first);
12997         DoSleep( appData.delayAfterQuit );
12998         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12999     }
13000     if (second.pr != NoProc) {
13001         DoSleep( appData.delayBeforeQuit );
13002         SendToProgram("quit\n", &second);
13003         DoSleep( appData.delayAfterQuit );
13004         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13005     }
13006     if (first.isr != NULL) {
13007         RemoveInputSource(first.isr);
13008     }
13009     if (second.isr != NULL) {
13010         RemoveInputSource(second.isr);
13011     }
13012
13013     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13014     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13015
13016     ShutDownFrontEnd();
13017     exit(status);
13018 }
13019
13020 void
13021 PauseEvent()
13022 {
13023     if (appData.debugMode)
13024         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13025     if (pausing) {
13026         pausing = FALSE;
13027         ModeHighlight();
13028         if (gameMode == MachinePlaysWhite ||
13029             gameMode == MachinePlaysBlack) {
13030             StartClocks();
13031         } else {
13032             DisplayBothClocks();
13033         }
13034         if (gameMode == PlayFromGameFile) {
13035             if (appData.timeDelay >= 0)
13036                 AutoPlayGameLoop();
13037         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13038             Reset(FALSE, TRUE);
13039             SendToICS(ics_prefix);
13040             SendToICS("refresh\n");
13041         } else if (currentMove < forwardMostMove) {
13042             ForwardInner(forwardMostMove);
13043         }
13044         pauseExamInvalid = FALSE;
13045     } else {
13046         switch (gameMode) {
13047           default:
13048             return;
13049           case IcsExamining:
13050             pauseExamForwardMostMove = forwardMostMove;
13051             pauseExamInvalid = FALSE;
13052             /* fall through */
13053           case IcsObserving:
13054           case IcsPlayingWhite:
13055           case IcsPlayingBlack:
13056             pausing = TRUE;
13057             ModeHighlight();
13058             return;
13059           case PlayFromGameFile:
13060             (void) StopLoadGameTimer();
13061             pausing = TRUE;
13062             ModeHighlight();
13063             break;
13064           case BeginningOfGame:
13065             if (appData.icsActive) return;
13066             /* else fall through */
13067           case MachinePlaysWhite:
13068           case MachinePlaysBlack:
13069           case TwoMachinesPlay:
13070             if (forwardMostMove == 0)
13071               return;           /* don't pause if no one has moved */
13072             if ((gameMode == MachinePlaysWhite &&
13073                  !WhiteOnMove(forwardMostMove)) ||
13074                 (gameMode == MachinePlaysBlack &&
13075                  WhiteOnMove(forwardMostMove))) {
13076                 StopClocks();
13077             }
13078             pausing = TRUE;
13079             ModeHighlight();
13080             break;
13081         }
13082     }
13083 }
13084
13085 void
13086 EditCommentEvent()
13087 {
13088     char title[MSG_SIZ];
13089
13090     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13091       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13092     } else {
13093       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13094                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13095                parseList[currentMove - 1]);
13096     }
13097
13098     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13099 }
13100
13101
13102 void
13103 EditTagsEvent()
13104 {
13105     char *tags = PGNTags(&gameInfo);
13106     bookUp = FALSE;
13107     EditTagsPopUp(tags, NULL);
13108     free(tags);
13109 }
13110
13111 void
13112 AnalyzeModeEvent()
13113 {
13114     if (appData.noChessProgram || gameMode == AnalyzeMode)
13115       return;
13116
13117     if (gameMode != AnalyzeFile) {
13118         if (!appData.icsEngineAnalyze) {
13119                EditGameEvent();
13120                if (gameMode != EditGame) return;
13121         }
13122         ResurrectChessProgram();
13123         SendToProgram("analyze\n", &first);
13124         first.analyzing = TRUE;
13125         /*first.maybeThinking = TRUE;*/
13126         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13127         EngineOutputPopUp();
13128     }
13129     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13130     pausing = FALSE;
13131     ModeHighlight();
13132     SetGameInfo();
13133
13134     StartAnalysisClock();
13135     GetTimeMark(&lastNodeCountTime);
13136     lastNodeCount = 0;
13137 }
13138
13139 void
13140 AnalyzeFileEvent()
13141 {
13142     if (appData.noChessProgram || gameMode == AnalyzeFile)
13143       return;
13144
13145     if (gameMode != AnalyzeMode) {
13146         EditGameEvent();
13147         if (gameMode != EditGame) return;
13148         ResurrectChessProgram();
13149         SendToProgram("analyze\n", &first);
13150         first.analyzing = TRUE;
13151         /*first.maybeThinking = TRUE;*/
13152         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13153         EngineOutputPopUp();
13154     }
13155     gameMode = AnalyzeFile;
13156     pausing = FALSE;
13157     ModeHighlight();
13158     SetGameInfo();
13159
13160     StartAnalysisClock();
13161     GetTimeMark(&lastNodeCountTime);
13162     lastNodeCount = 0;
13163     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13164 }
13165
13166 void
13167 MachineWhiteEvent()
13168 {
13169     char buf[MSG_SIZ];
13170     char *bookHit = NULL;
13171
13172     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13173       return;
13174
13175
13176     if (gameMode == PlayFromGameFile ||
13177         gameMode == TwoMachinesPlay  ||
13178         gameMode == Training         ||
13179         gameMode == AnalyzeMode      ||
13180         gameMode == EndOfGame)
13181         EditGameEvent();
13182
13183     if (gameMode == EditPosition)
13184         EditPositionDone(TRUE);
13185
13186     if (!WhiteOnMove(currentMove)) {
13187         DisplayError(_("It is not White's turn"), 0);
13188         return;
13189     }
13190
13191     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13192       ExitAnalyzeMode();
13193
13194     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13195         gameMode == AnalyzeFile)
13196         TruncateGame();
13197
13198     ResurrectChessProgram();    /* in case it isn't running */
13199     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13200         gameMode = MachinePlaysWhite;
13201         ResetClocks();
13202     } else
13203     gameMode = MachinePlaysWhite;
13204     pausing = FALSE;
13205     ModeHighlight();
13206     SetGameInfo();
13207     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13208     DisplayTitle(buf);
13209     if (first.sendName) {
13210       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13211       SendToProgram(buf, &first);
13212     }
13213     if (first.sendTime) {
13214       if (first.useColors) {
13215         SendToProgram("black\n", &first); /*gnu kludge*/
13216       }
13217       SendTimeRemaining(&first, TRUE);
13218     }
13219     if (first.useColors) {
13220       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13221     }
13222     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13223     SetMachineThinkingEnables();
13224     first.maybeThinking = TRUE;
13225     StartClocks();
13226     firstMove = FALSE;
13227
13228     if (appData.autoFlipView && !flipView) {
13229       flipView = !flipView;
13230       DrawPosition(FALSE, NULL);
13231       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13232     }
13233
13234     if(bookHit) { // [HGM] book: simulate book reply
13235         static char bookMove[MSG_SIZ]; // a bit generous?
13236
13237         programStats.nodes = programStats.depth = programStats.time =
13238         programStats.score = programStats.got_only_move = 0;
13239         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13240
13241         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13242         strcat(bookMove, bookHit);
13243         HandleMachineMove(bookMove, &first);
13244     }
13245 }
13246
13247 void
13248 MachineBlackEvent()
13249 {
13250   char buf[MSG_SIZ];
13251   char *bookHit = NULL;
13252
13253     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13254         return;
13255
13256
13257     if (gameMode == PlayFromGameFile ||
13258         gameMode == TwoMachinesPlay  ||
13259         gameMode == Training         ||
13260         gameMode == AnalyzeMode      ||
13261         gameMode == EndOfGame)
13262         EditGameEvent();
13263
13264     if (gameMode == EditPosition)
13265         EditPositionDone(TRUE);
13266
13267     if (WhiteOnMove(currentMove)) {
13268         DisplayError(_("It is not Black's turn"), 0);
13269         return;
13270     }
13271
13272     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13273       ExitAnalyzeMode();
13274
13275     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13276         gameMode == AnalyzeFile)
13277         TruncateGame();
13278
13279     ResurrectChessProgram();    /* in case it isn't running */
13280     gameMode = MachinePlaysBlack;
13281     pausing = FALSE;
13282     ModeHighlight();
13283     SetGameInfo();
13284     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13285     DisplayTitle(buf);
13286     if (first.sendName) {
13287       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13288       SendToProgram(buf, &first);
13289     }
13290     if (first.sendTime) {
13291       if (first.useColors) {
13292         SendToProgram("white\n", &first); /*gnu kludge*/
13293       }
13294       SendTimeRemaining(&first, FALSE);
13295     }
13296     if (first.useColors) {
13297       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13298     }
13299     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13300     SetMachineThinkingEnables();
13301     first.maybeThinking = TRUE;
13302     StartClocks();
13303
13304     if (appData.autoFlipView && flipView) {
13305       flipView = !flipView;
13306       DrawPosition(FALSE, NULL);
13307       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13308     }
13309     if(bookHit) { // [HGM] book: simulate book reply
13310         static char bookMove[MSG_SIZ]; // a bit generous?
13311
13312         programStats.nodes = programStats.depth = programStats.time =
13313         programStats.score = programStats.got_only_move = 0;
13314         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13315
13316         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13317         strcat(bookMove, bookHit);
13318         HandleMachineMove(bookMove, &first);
13319     }
13320 }
13321
13322
13323 void
13324 DisplayTwoMachinesTitle()
13325 {
13326     char buf[MSG_SIZ];
13327     if (appData.matchGames > 0) {
13328         if(appData.tourneyFile[0]) {
13329           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13330                    gameInfo.white, gameInfo.black,
13331                    nextGame+1, appData.matchGames+1,
13332                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13333         } else 
13334         if (first.twoMachinesColor[0] == 'w') {
13335           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13336                    gameInfo.white, gameInfo.black,
13337                    first.matchWins, second.matchWins,
13338                    matchGame - 1 - (first.matchWins + second.matchWins));
13339         } else {
13340           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13341                    gameInfo.white, gameInfo.black,
13342                    second.matchWins, first.matchWins,
13343                    matchGame - 1 - (first.matchWins + second.matchWins));
13344         }
13345     } else {
13346       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13347     }
13348     DisplayTitle(buf);
13349 }
13350
13351 void
13352 SettingsMenuIfReady()
13353 {
13354   if (second.lastPing != second.lastPong) {
13355     DisplayMessage("", _("Waiting for second chess program"));
13356     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13357     return;
13358   }
13359   ThawUI();
13360   DisplayMessage("", "");
13361   SettingsPopUp(&second);
13362 }
13363
13364 int
13365 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13366 {
13367     char buf[MSG_SIZ];
13368     if (cps->pr == NoProc) {
13369         StartChessProgram(cps);
13370         if (cps->protocolVersion == 1) {
13371           retry();
13372         } else {
13373           /* kludge: allow timeout for initial "feature" command */
13374           FreezeUI();
13375           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13376           DisplayMessage("", buf);
13377           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13378         }
13379         return 1;
13380     }
13381     return 0;
13382 }
13383
13384 void
13385 TwoMachinesEvent P((void))
13386 {
13387     int i;
13388     char buf[MSG_SIZ];
13389     ChessProgramState *onmove;
13390     char *bookHit = NULL;
13391     static int stalling = 0;
13392     TimeMark now;
13393     long wait;
13394
13395     if (appData.noChessProgram) return;
13396
13397     switch (gameMode) {
13398       case TwoMachinesPlay:
13399         return;
13400       case MachinePlaysWhite:
13401       case MachinePlaysBlack:
13402         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13403             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13404             return;
13405         }
13406         /* fall through */
13407       case BeginningOfGame:
13408       case PlayFromGameFile:
13409       case EndOfGame:
13410         EditGameEvent();
13411         if (gameMode != EditGame) return;
13412         break;
13413       case EditPosition:
13414         EditPositionDone(TRUE);
13415         break;
13416       case AnalyzeMode:
13417       case AnalyzeFile:
13418         ExitAnalyzeMode();
13419         break;
13420       case EditGame:
13421       default:
13422         break;
13423     }
13424
13425 //    forwardMostMove = currentMove;
13426     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13427
13428     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13429
13430     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13431     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13432       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13433       return;
13434     }
13435     if(!stalling) {
13436       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13437       SendToProgram("force\n", &second);
13438       stalling = 1;
13439       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13440       return;
13441     }
13442     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13443     if(appData.matchPause>10000 || appData.matchPause<10)
13444                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13445     wait = SubtractTimeMarks(&now, &pauseStart);
13446     if(wait < appData.matchPause) {
13447         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13448         return;
13449     }
13450     stalling = 0;
13451     DisplayMessage("", "");
13452     if (startedFromSetupPosition) {
13453         SendBoard(&second, backwardMostMove);
13454     if (appData.debugMode) {
13455         fprintf(debugFP, "Two Machines\n");
13456     }
13457     }
13458     for (i = backwardMostMove; i < forwardMostMove; i++) {
13459         SendMoveToProgram(i, &second);
13460     }
13461
13462     gameMode = TwoMachinesPlay;
13463     pausing = FALSE;
13464     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13465     SetGameInfo();
13466     DisplayTwoMachinesTitle();
13467     firstMove = TRUE;
13468     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13469         onmove = &first;
13470     } else {
13471         onmove = &second;
13472     }
13473     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13474     SendToProgram(first.computerString, &first);
13475     if (first.sendName) {
13476       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13477       SendToProgram(buf, &first);
13478     }
13479     SendToProgram(second.computerString, &second);
13480     if (second.sendName) {
13481       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13482       SendToProgram(buf, &second);
13483     }
13484
13485     ResetClocks();
13486     if (!first.sendTime || !second.sendTime) {
13487         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13488         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13489     }
13490     if (onmove->sendTime) {
13491       if (onmove->useColors) {
13492         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13493       }
13494       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13495     }
13496     if (onmove->useColors) {
13497       SendToProgram(onmove->twoMachinesColor, onmove);
13498     }
13499     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13500 //    SendToProgram("go\n", onmove);
13501     onmove->maybeThinking = TRUE;
13502     SetMachineThinkingEnables();
13503
13504     StartClocks();
13505
13506     if(bookHit) { // [HGM] book: simulate book reply
13507         static char bookMove[MSG_SIZ]; // a bit generous?
13508
13509         programStats.nodes = programStats.depth = programStats.time =
13510         programStats.score = programStats.got_only_move = 0;
13511         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13512
13513         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13514         strcat(bookMove, bookHit);
13515         savedMessage = bookMove; // args for deferred call
13516         savedState = onmove;
13517         ScheduleDelayedEvent(DeferredBookMove, 1);
13518     }
13519 }
13520
13521 void
13522 TrainingEvent()
13523 {
13524     if (gameMode == Training) {
13525       SetTrainingModeOff();
13526       gameMode = PlayFromGameFile;
13527       DisplayMessage("", _("Training mode off"));
13528     } else {
13529       gameMode = Training;
13530       animateTraining = appData.animate;
13531
13532       /* make sure we are not already at the end of the game */
13533       if (currentMove < forwardMostMove) {
13534         SetTrainingModeOn();
13535         DisplayMessage("", _("Training mode on"));
13536       } else {
13537         gameMode = PlayFromGameFile;
13538         DisplayError(_("Already at end of game"), 0);
13539       }
13540     }
13541     ModeHighlight();
13542 }
13543
13544 void
13545 IcsClientEvent()
13546 {
13547     if (!appData.icsActive) return;
13548     switch (gameMode) {
13549       case IcsPlayingWhite:
13550       case IcsPlayingBlack:
13551       case IcsObserving:
13552       case IcsIdle:
13553       case BeginningOfGame:
13554       case IcsExamining:
13555         return;
13556
13557       case EditGame:
13558         break;
13559
13560       case EditPosition:
13561         EditPositionDone(TRUE);
13562         break;
13563
13564       case AnalyzeMode:
13565       case AnalyzeFile:
13566         ExitAnalyzeMode();
13567         break;
13568
13569       default:
13570         EditGameEvent();
13571         break;
13572     }
13573
13574     gameMode = IcsIdle;
13575     ModeHighlight();
13576     return;
13577 }
13578
13579
13580 void
13581 EditGameEvent()
13582 {
13583     int i;
13584
13585     switch (gameMode) {
13586       case Training:
13587         SetTrainingModeOff();
13588         break;
13589       case MachinePlaysWhite:
13590       case MachinePlaysBlack:
13591       case BeginningOfGame:
13592         SendToProgram("force\n", &first);
13593         SetUserThinkingEnables();
13594         break;
13595       case PlayFromGameFile:
13596         (void) StopLoadGameTimer();
13597         if (gameFileFP != NULL) {
13598             gameFileFP = NULL;
13599         }
13600         break;
13601       case EditPosition:
13602         EditPositionDone(TRUE);
13603         break;
13604       case AnalyzeMode:
13605       case AnalyzeFile:
13606         ExitAnalyzeMode();
13607         SendToProgram("force\n", &first);
13608         break;
13609       case TwoMachinesPlay:
13610         GameEnds(EndOfFile, NULL, GE_PLAYER);
13611         ResurrectChessProgram();
13612         SetUserThinkingEnables();
13613         break;
13614       case EndOfGame:
13615         ResurrectChessProgram();
13616         break;
13617       case IcsPlayingBlack:
13618       case IcsPlayingWhite:
13619         DisplayError(_("Warning: You are still playing a game"), 0);
13620         break;
13621       case IcsObserving:
13622         DisplayError(_("Warning: You are still observing a game"), 0);
13623         break;
13624       case IcsExamining:
13625         DisplayError(_("Warning: You are still examining a game"), 0);
13626         break;
13627       case IcsIdle:
13628         break;
13629       case EditGame:
13630       default:
13631         return;
13632     }
13633
13634     pausing = FALSE;
13635     StopClocks();
13636     first.offeredDraw = second.offeredDraw = 0;
13637
13638     if (gameMode == PlayFromGameFile) {
13639         whiteTimeRemaining = timeRemaining[0][currentMove];
13640         blackTimeRemaining = timeRemaining[1][currentMove];
13641         DisplayTitle("");
13642     }
13643
13644     if (gameMode == MachinePlaysWhite ||
13645         gameMode == MachinePlaysBlack ||
13646         gameMode == TwoMachinesPlay ||
13647         gameMode == EndOfGame) {
13648         i = forwardMostMove;
13649         while (i > currentMove) {
13650             SendToProgram("undo\n", &first);
13651             i--;
13652         }
13653         if(!adjustedClock) {
13654         whiteTimeRemaining = timeRemaining[0][currentMove];
13655         blackTimeRemaining = timeRemaining[1][currentMove];
13656         DisplayBothClocks();
13657         }
13658         if (whiteFlag || blackFlag) {
13659             whiteFlag = blackFlag = 0;
13660         }
13661         DisplayTitle("");
13662     }
13663
13664     gameMode = EditGame;
13665     ModeHighlight();
13666     SetGameInfo();
13667 }
13668
13669
13670 void
13671 EditPositionEvent()
13672 {
13673     if (gameMode == EditPosition) {
13674         EditGameEvent();
13675         return;
13676     }
13677
13678     EditGameEvent();
13679     if (gameMode != EditGame) return;
13680
13681     gameMode = EditPosition;
13682     ModeHighlight();
13683     SetGameInfo();
13684     if (currentMove > 0)
13685       CopyBoard(boards[0], boards[currentMove]);
13686
13687     blackPlaysFirst = !WhiteOnMove(currentMove);
13688     ResetClocks();
13689     currentMove = forwardMostMove = backwardMostMove = 0;
13690     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13691     DisplayMove(-1);
13692 }
13693
13694 void
13695 ExitAnalyzeMode()
13696 {
13697     /* [DM] icsEngineAnalyze - possible call from other functions */
13698     if (appData.icsEngineAnalyze) {
13699         appData.icsEngineAnalyze = FALSE;
13700
13701         DisplayMessage("",_("Close ICS engine analyze..."));
13702     }
13703     if (first.analysisSupport && first.analyzing) {
13704       SendToProgram("exit\n", &first);
13705       first.analyzing = FALSE;
13706     }
13707     thinkOutput[0] = NULLCHAR;
13708 }
13709
13710 void
13711 EditPositionDone(Boolean fakeRights)
13712 {
13713     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13714
13715     startedFromSetupPosition = TRUE;
13716     InitChessProgram(&first, FALSE);
13717     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13718       boards[0][EP_STATUS] = EP_NONE;
13719       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13720     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13721         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13722         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13723       } else boards[0][CASTLING][2] = NoRights;
13724     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13725         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13726         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13727       } else boards[0][CASTLING][5] = NoRights;
13728     }
13729     SendToProgram("force\n", &first);
13730     if (blackPlaysFirst) {
13731         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13732         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13733         currentMove = forwardMostMove = backwardMostMove = 1;
13734         CopyBoard(boards[1], boards[0]);
13735     } else {
13736         currentMove = forwardMostMove = backwardMostMove = 0;
13737     }
13738     SendBoard(&first, forwardMostMove);
13739     if (appData.debugMode) {
13740         fprintf(debugFP, "EditPosDone\n");
13741     }
13742     DisplayTitle("");
13743     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13744     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13745     gameMode = EditGame;
13746     ModeHighlight();
13747     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13748     ClearHighlights(); /* [AS] */
13749 }
13750
13751 /* Pause for `ms' milliseconds */
13752 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13753 void
13754 TimeDelay(ms)
13755      long ms;
13756 {
13757     TimeMark m1, m2;
13758
13759     GetTimeMark(&m1);
13760     do {
13761         GetTimeMark(&m2);
13762     } while (SubtractTimeMarks(&m2, &m1) < ms);
13763 }
13764
13765 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13766 void
13767 SendMultiLineToICS(buf)
13768      char *buf;
13769 {
13770     char temp[MSG_SIZ+1], *p;
13771     int len;
13772
13773     len = strlen(buf);
13774     if (len > MSG_SIZ)
13775       len = MSG_SIZ;
13776
13777     strncpy(temp, buf, len);
13778     temp[len] = 0;
13779
13780     p = temp;
13781     while (*p) {
13782         if (*p == '\n' || *p == '\r')
13783           *p = ' ';
13784         ++p;
13785     }
13786
13787     strcat(temp, "\n");
13788     SendToICS(temp);
13789     SendToPlayer(temp, strlen(temp));
13790 }
13791
13792 void
13793 SetWhiteToPlayEvent()
13794 {
13795     if (gameMode == EditPosition) {
13796         blackPlaysFirst = FALSE;
13797         DisplayBothClocks();    /* works because currentMove is 0 */
13798     } else if (gameMode == IcsExamining) {
13799         SendToICS(ics_prefix);
13800         SendToICS("tomove white\n");
13801     }
13802 }
13803
13804 void
13805 SetBlackToPlayEvent()
13806 {
13807     if (gameMode == EditPosition) {
13808         blackPlaysFirst = TRUE;
13809         currentMove = 1;        /* kludge */
13810         DisplayBothClocks();
13811         currentMove = 0;
13812     } else if (gameMode == IcsExamining) {
13813         SendToICS(ics_prefix);
13814         SendToICS("tomove black\n");
13815     }
13816 }
13817
13818 void
13819 EditPositionMenuEvent(selection, x, y)
13820      ChessSquare selection;
13821      int x, y;
13822 {
13823     char buf[MSG_SIZ];
13824     ChessSquare piece = boards[0][y][x];
13825
13826     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13827
13828     switch (selection) {
13829       case ClearBoard:
13830         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13831             SendToICS(ics_prefix);
13832             SendToICS("bsetup clear\n");
13833         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13834             SendToICS(ics_prefix);
13835             SendToICS("clearboard\n");
13836         } else {
13837             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13838                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13839                 for (y = 0; y < BOARD_HEIGHT; y++) {
13840                     if (gameMode == IcsExamining) {
13841                         if (boards[currentMove][y][x] != EmptySquare) {
13842                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13843                                     AAA + x, ONE + y);
13844                             SendToICS(buf);
13845                         }
13846                     } else {
13847                         boards[0][y][x] = p;
13848                     }
13849                 }
13850             }
13851         }
13852         if (gameMode == EditPosition) {
13853             DrawPosition(FALSE, boards[0]);
13854         }
13855         break;
13856
13857       case WhitePlay:
13858         SetWhiteToPlayEvent();
13859         break;
13860
13861       case BlackPlay:
13862         SetBlackToPlayEvent();
13863         break;
13864
13865       case EmptySquare:
13866         if (gameMode == IcsExamining) {
13867             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13868             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13869             SendToICS(buf);
13870         } else {
13871             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13872                 if(x == BOARD_LEFT-2) {
13873                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13874                     boards[0][y][1] = 0;
13875                 } else
13876                 if(x == BOARD_RGHT+1) {
13877                     if(y >= gameInfo.holdingsSize) break;
13878                     boards[0][y][BOARD_WIDTH-2] = 0;
13879                 } else break;
13880             }
13881             boards[0][y][x] = EmptySquare;
13882             DrawPosition(FALSE, boards[0]);
13883         }
13884         break;
13885
13886       case PromotePiece:
13887         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13888            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13889             selection = (ChessSquare) (PROMOTED piece);
13890         } else if(piece == EmptySquare) selection = WhiteSilver;
13891         else selection = (ChessSquare)((int)piece - 1);
13892         goto defaultlabel;
13893
13894       case DemotePiece:
13895         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13896            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13897             selection = (ChessSquare) (DEMOTED piece);
13898         } else if(piece == EmptySquare) selection = BlackSilver;
13899         else selection = (ChessSquare)((int)piece + 1);
13900         goto defaultlabel;
13901
13902       case WhiteQueen:
13903       case BlackQueen:
13904         if(gameInfo.variant == VariantShatranj ||
13905            gameInfo.variant == VariantXiangqi  ||
13906            gameInfo.variant == VariantCourier  ||
13907            gameInfo.variant == VariantMakruk     )
13908             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13909         goto defaultlabel;
13910
13911       case WhiteKing:
13912       case BlackKing:
13913         if(gameInfo.variant == VariantXiangqi)
13914             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13915         if(gameInfo.variant == VariantKnightmate)
13916             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13917       default:
13918         defaultlabel:
13919         if (gameMode == IcsExamining) {
13920             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13921             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13922                      PieceToChar(selection), AAA + x, ONE + y);
13923             SendToICS(buf);
13924         } else {
13925             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13926                 int n;
13927                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13928                     n = PieceToNumber(selection - BlackPawn);
13929                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13930                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13931                     boards[0][BOARD_HEIGHT-1-n][1]++;
13932                 } else
13933                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13934                     n = PieceToNumber(selection);
13935                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13936                     boards[0][n][BOARD_WIDTH-1] = selection;
13937                     boards[0][n][BOARD_WIDTH-2]++;
13938                 }
13939             } else
13940             boards[0][y][x] = selection;
13941             DrawPosition(TRUE, boards[0]);
13942         }
13943         break;
13944     }
13945 }
13946
13947
13948 void
13949 DropMenuEvent(selection, x, y)
13950      ChessSquare selection;
13951      int x, y;
13952 {
13953     ChessMove moveType;
13954
13955     switch (gameMode) {
13956       case IcsPlayingWhite:
13957       case MachinePlaysBlack:
13958         if (!WhiteOnMove(currentMove)) {
13959             DisplayMoveError(_("It is Black's turn"));
13960             return;
13961         }
13962         moveType = WhiteDrop;
13963         break;
13964       case IcsPlayingBlack:
13965       case MachinePlaysWhite:
13966         if (WhiteOnMove(currentMove)) {
13967             DisplayMoveError(_("It is White's turn"));
13968             return;
13969         }
13970         moveType = BlackDrop;
13971         break;
13972       case EditGame:
13973         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13974         break;
13975       default:
13976         return;
13977     }
13978
13979     if (moveType == BlackDrop && selection < BlackPawn) {
13980       selection = (ChessSquare) ((int) selection
13981                                  + (int) BlackPawn - (int) WhitePawn);
13982     }
13983     if (boards[currentMove][y][x] != EmptySquare) {
13984         DisplayMoveError(_("That square is occupied"));
13985         return;
13986     }
13987
13988     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13989 }
13990
13991 void
13992 AcceptEvent()
13993 {
13994     /* Accept a pending offer of any kind from opponent */
13995
13996     if (appData.icsActive) {
13997         SendToICS(ics_prefix);
13998         SendToICS("accept\n");
13999     } else if (cmailMsgLoaded) {
14000         if (currentMove == cmailOldMove &&
14001             commentList[cmailOldMove] != NULL &&
14002             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14003                    "Black offers a draw" : "White offers a draw")) {
14004             TruncateGame();
14005             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14006             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14007         } else {
14008             DisplayError(_("There is no pending offer on this move"), 0);
14009             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14010         }
14011     } else {
14012         /* Not used for offers from chess program */
14013     }
14014 }
14015
14016 void
14017 DeclineEvent()
14018 {
14019     /* Decline a pending offer of any kind from opponent */
14020
14021     if (appData.icsActive) {
14022         SendToICS(ics_prefix);
14023         SendToICS("decline\n");
14024     } else if (cmailMsgLoaded) {
14025         if (currentMove == cmailOldMove &&
14026             commentList[cmailOldMove] != NULL &&
14027             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14028                    "Black offers a draw" : "White offers a draw")) {
14029 #ifdef NOTDEF
14030             AppendComment(cmailOldMove, "Draw declined", TRUE);
14031             DisplayComment(cmailOldMove - 1, "Draw declined");
14032 #endif /*NOTDEF*/
14033         } else {
14034             DisplayError(_("There is no pending offer on this move"), 0);
14035         }
14036     } else {
14037         /* Not used for offers from chess program */
14038     }
14039 }
14040
14041 void
14042 RematchEvent()
14043 {
14044     /* Issue ICS rematch command */
14045     if (appData.icsActive) {
14046         SendToICS(ics_prefix);
14047         SendToICS("rematch\n");
14048     }
14049 }
14050
14051 void
14052 CallFlagEvent()
14053 {
14054     /* Call your opponent's flag (claim a win on time) */
14055     if (appData.icsActive) {
14056         SendToICS(ics_prefix);
14057         SendToICS("flag\n");
14058     } else {
14059         switch (gameMode) {
14060           default:
14061             return;
14062           case MachinePlaysWhite:
14063             if (whiteFlag) {
14064                 if (blackFlag)
14065                   GameEnds(GameIsDrawn, "Both players ran out of time",
14066                            GE_PLAYER);
14067                 else
14068                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14069             } else {
14070                 DisplayError(_("Your opponent is not out of time"), 0);
14071             }
14072             break;
14073           case MachinePlaysBlack:
14074             if (blackFlag) {
14075                 if (whiteFlag)
14076                   GameEnds(GameIsDrawn, "Both players ran out of time",
14077                            GE_PLAYER);
14078                 else
14079                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14080             } else {
14081                 DisplayError(_("Your opponent is not out of time"), 0);
14082             }
14083             break;
14084         }
14085     }
14086 }
14087
14088 void
14089 ClockClick(int which)
14090 {       // [HGM] code moved to back-end from winboard.c
14091         if(which) { // black clock
14092           if (gameMode == EditPosition || gameMode == IcsExamining) {
14093             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14094             SetBlackToPlayEvent();
14095           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14096           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14097           } else if (shiftKey) {
14098             AdjustClock(which, -1);
14099           } else if (gameMode == IcsPlayingWhite ||
14100                      gameMode == MachinePlaysBlack) {
14101             CallFlagEvent();
14102           }
14103         } else { // white clock
14104           if (gameMode == EditPosition || gameMode == IcsExamining) {
14105             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14106             SetWhiteToPlayEvent();
14107           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14108           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14109           } else if (shiftKey) {
14110             AdjustClock(which, -1);
14111           } else if (gameMode == IcsPlayingBlack ||
14112                    gameMode == MachinePlaysWhite) {
14113             CallFlagEvent();
14114           }
14115         }
14116 }
14117
14118 void
14119 DrawEvent()
14120 {
14121     /* Offer draw or accept pending draw offer from opponent */
14122
14123     if (appData.icsActive) {
14124         /* Note: tournament rules require draw offers to be
14125            made after you make your move but before you punch
14126            your clock.  Currently ICS doesn't let you do that;
14127            instead, you immediately punch your clock after making
14128            a move, but you can offer a draw at any time. */
14129
14130         SendToICS(ics_prefix);
14131         SendToICS("draw\n");
14132         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14133     } else if (cmailMsgLoaded) {
14134         if (currentMove == cmailOldMove &&
14135             commentList[cmailOldMove] != NULL &&
14136             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14137                    "Black offers a draw" : "White offers a draw")) {
14138             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14139             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14140         } else if (currentMove == cmailOldMove + 1) {
14141             char *offer = WhiteOnMove(cmailOldMove) ?
14142               "White offers a draw" : "Black offers a draw";
14143             AppendComment(currentMove, offer, TRUE);
14144             DisplayComment(currentMove - 1, offer);
14145             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14146         } else {
14147             DisplayError(_("You must make your move before offering a draw"), 0);
14148             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14149         }
14150     } else if (first.offeredDraw) {
14151         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14152     } else {
14153         if (first.sendDrawOffers) {
14154             SendToProgram("draw\n", &first);
14155             userOfferedDraw = TRUE;
14156         }
14157     }
14158 }
14159
14160 void
14161 AdjournEvent()
14162 {
14163     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14164
14165     if (appData.icsActive) {
14166         SendToICS(ics_prefix);
14167         SendToICS("adjourn\n");
14168     } else {
14169         /* Currently GNU Chess doesn't offer or accept Adjourns */
14170     }
14171 }
14172
14173
14174 void
14175 AbortEvent()
14176 {
14177     /* Offer Abort or accept pending Abort offer from opponent */
14178
14179     if (appData.icsActive) {
14180         SendToICS(ics_prefix);
14181         SendToICS("abort\n");
14182     } else {
14183         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14184     }
14185 }
14186
14187 void
14188 ResignEvent()
14189 {
14190     /* Resign.  You can do this even if it's not your turn. */
14191
14192     if (appData.icsActive) {
14193         SendToICS(ics_prefix);
14194         SendToICS("resign\n");
14195     } else {
14196         switch (gameMode) {
14197           case MachinePlaysWhite:
14198             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14199             break;
14200           case MachinePlaysBlack:
14201             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14202             break;
14203           case EditGame:
14204             if (cmailMsgLoaded) {
14205                 TruncateGame();
14206                 if (WhiteOnMove(cmailOldMove)) {
14207                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14208                 } else {
14209                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14210                 }
14211                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14212             }
14213             break;
14214           default:
14215             break;
14216         }
14217     }
14218 }
14219
14220
14221 void
14222 StopObservingEvent()
14223 {
14224     /* Stop observing current games */
14225     SendToICS(ics_prefix);
14226     SendToICS("unobserve\n");
14227 }
14228
14229 void
14230 StopExaminingEvent()
14231 {
14232     /* Stop observing current game */
14233     SendToICS(ics_prefix);
14234     SendToICS("unexamine\n");
14235 }
14236
14237 void
14238 ForwardInner(target)
14239      int target;
14240 {
14241     int limit;
14242
14243     if (appData.debugMode)
14244         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14245                 target, currentMove, forwardMostMove);
14246
14247     if (gameMode == EditPosition)
14248       return;
14249
14250     MarkTargetSquares(1);
14251
14252     if (gameMode == PlayFromGameFile && !pausing)
14253       PauseEvent();
14254
14255     if (gameMode == IcsExamining && pausing)
14256       limit = pauseExamForwardMostMove;
14257     else
14258       limit = forwardMostMove;
14259
14260     if (target > limit) target = limit;
14261
14262     if (target > 0 && moveList[target - 1][0]) {
14263         int fromX, fromY, toX, toY;
14264         toX = moveList[target - 1][2] - AAA;
14265         toY = moveList[target - 1][3] - ONE;
14266         if (moveList[target - 1][1] == '@') {
14267             if (appData.highlightLastMove) {
14268                 SetHighlights(-1, -1, toX, toY);
14269             }
14270         } else {
14271             fromX = moveList[target - 1][0] - AAA;
14272             fromY = moveList[target - 1][1] - ONE;
14273             if (target == currentMove + 1) {
14274                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14275             }
14276             if (appData.highlightLastMove) {
14277                 SetHighlights(fromX, fromY, toX, toY);
14278             }
14279         }
14280     }
14281     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14282         gameMode == Training || gameMode == PlayFromGameFile ||
14283         gameMode == AnalyzeFile) {
14284         while (currentMove < target) {
14285             SendMoveToProgram(currentMove++, &first);
14286         }
14287     } else {
14288         currentMove = target;
14289     }
14290
14291     if (gameMode == EditGame || gameMode == EndOfGame) {
14292         whiteTimeRemaining = timeRemaining[0][currentMove];
14293         blackTimeRemaining = timeRemaining[1][currentMove];
14294     }
14295     DisplayBothClocks();
14296     DisplayMove(currentMove - 1);
14297     DrawPosition(FALSE, boards[currentMove]);
14298     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14299     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14300         DisplayComment(currentMove - 1, commentList[currentMove]);
14301     }
14302 }
14303
14304
14305 void
14306 ForwardEvent()
14307 {
14308     if (gameMode == IcsExamining && !pausing) {
14309         SendToICS(ics_prefix);
14310         SendToICS("forward\n");
14311     } else {
14312         ForwardInner(currentMove + 1);
14313     }
14314 }
14315
14316 void
14317 ToEndEvent()
14318 {
14319     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14320         /* to optimze, we temporarily turn off analysis mode while we feed
14321          * the remaining moves to the engine. Otherwise we get analysis output
14322          * after each move.
14323          */
14324         if (first.analysisSupport) {
14325           SendToProgram("exit\nforce\n", &first);
14326           first.analyzing = FALSE;
14327         }
14328     }
14329
14330     if (gameMode == IcsExamining && !pausing) {
14331         SendToICS(ics_prefix);
14332         SendToICS("forward 999999\n");
14333     } else {
14334         ForwardInner(forwardMostMove);
14335     }
14336
14337     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14338         /* we have fed all the moves, so reactivate analysis mode */
14339         SendToProgram("analyze\n", &first);
14340         first.analyzing = TRUE;
14341         /*first.maybeThinking = TRUE;*/
14342         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14343     }
14344 }
14345
14346 void
14347 BackwardInner(target)
14348      int target;
14349 {
14350     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14351
14352     if (appData.debugMode)
14353         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14354                 target, currentMove, forwardMostMove);
14355
14356     if (gameMode == EditPosition) return;
14357     MarkTargetSquares(1);
14358     if (currentMove <= backwardMostMove) {
14359         ClearHighlights();
14360         DrawPosition(full_redraw, boards[currentMove]);
14361         return;
14362     }
14363     if (gameMode == PlayFromGameFile && !pausing)
14364       PauseEvent();
14365
14366     if (moveList[target][0]) {
14367         int fromX, fromY, toX, toY;
14368         toX = moveList[target][2] - AAA;
14369         toY = moveList[target][3] - ONE;
14370         if (moveList[target][1] == '@') {
14371             if (appData.highlightLastMove) {
14372                 SetHighlights(-1, -1, toX, toY);
14373             }
14374         } else {
14375             fromX = moveList[target][0] - AAA;
14376             fromY = moveList[target][1] - ONE;
14377             if (target == currentMove - 1) {
14378                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14379             }
14380             if (appData.highlightLastMove) {
14381                 SetHighlights(fromX, fromY, toX, toY);
14382             }
14383         }
14384     }
14385     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14386         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14387         while (currentMove > target) {
14388             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14389                 // null move cannot be undone. Reload program with move history before it.
14390                 int i;
14391                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14392                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14393                 }
14394                 SendBoard(&first, i); 
14395                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14396                 break;
14397             }
14398             SendToProgram("undo\n", &first);
14399             currentMove--;
14400         }
14401     } else {
14402         currentMove = target;
14403     }
14404
14405     if (gameMode == EditGame || gameMode == EndOfGame) {
14406         whiteTimeRemaining = timeRemaining[0][currentMove];
14407         blackTimeRemaining = timeRemaining[1][currentMove];
14408     }
14409     DisplayBothClocks();
14410     DisplayMove(currentMove - 1);
14411     DrawPosition(full_redraw, boards[currentMove]);
14412     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14413     // [HGM] PV info: routine tests if comment empty
14414     DisplayComment(currentMove - 1, commentList[currentMove]);
14415 }
14416
14417 void
14418 BackwardEvent()
14419 {
14420     if (gameMode == IcsExamining && !pausing) {
14421         SendToICS(ics_prefix);
14422         SendToICS("backward\n");
14423     } else {
14424         BackwardInner(currentMove - 1);
14425     }
14426 }
14427
14428 void
14429 ToStartEvent()
14430 {
14431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14432         /* to optimize, we temporarily turn off analysis mode while we undo
14433          * all the moves. Otherwise we get analysis output after each undo.
14434          */
14435         if (first.analysisSupport) {
14436           SendToProgram("exit\nforce\n", &first);
14437           first.analyzing = FALSE;
14438         }
14439     }
14440
14441     if (gameMode == IcsExamining && !pausing) {
14442         SendToICS(ics_prefix);
14443         SendToICS("backward 999999\n");
14444     } else {
14445         BackwardInner(backwardMostMove);
14446     }
14447
14448     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14449         /* we have fed all the moves, so reactivate analysis mode */
14450         SendToProgram("analyze\n", &first);
14451         first.analyzing = TRUE;
14452         /*first.maybeThinking = TRUE;*/
14453         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14454     }
14455 }
14456
14457 void
14458 ToNrEvent(int to)
14459 {
14460   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14461   if (to >= forwardMostMove) to = forwardMostMove;
14462   if (to <= backwardMostMove) to = backwardMostMove;
14463   if (to < currentMove) {
14464     BackwardInner(to);
14465   } else {
14466     ForwardInner(to);
14467   }
14468 }
14469
14470 void
14471 RevertEvent(Boolean annotate)
14472 {
14473     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14474         return;
14475     }
14476     if (gameMode != IcsExamining) {
14477         DisplayError(_("You are not examining a game"), 0);
14478         return;
14479     }
14480     if (pausing) {
14481         DisplayError(_("You can't revert while pausing"), 0);
14482         return;
14483     }
14484     SendToICS(ics_prefix);
14485     SendToICS("revert\n");
14486 }
14487
14488 void
14489 RetractMoveEvent()
14490 {
14491     switch (gameMode) {
14492       case MachinePlaysWhite:
14493       case MachinePlaysBlack:
14494         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14495             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14496             return;
14497         }
14498         if (forwardMostMove < 2) return;
14499         currentMove = forwardMostMove = forwardMostMove - 2;
14500         whiteTimeRemaining = timeRemaining[0][currentMove];
14501         blackTimeRemaining = timeRemaining[1][currentMove];
14502         DisplayBothClocks();
14503         DisplayMove(currentMove - 1);
14504         ClearHighlights();/*!! could figure this out*/
14505         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14506         SendToProgram("remove\n", &first);
14507         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14508         break;
14509
14510       case BeginningOfGame:
14511       default:
14512         break;
14513
14514       case IcsPlayingWhite:
14515       case IcsPlayingBlack:
14516         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14517             SendToICS(ics_prefix);
14518             SendToICS("takeback 2\n");
14519         } else {
14520             SendToICS(ics_prefix);
14521             SendToICS("takeback 1\n");
14522         }
14523         break;
14524     }
14525 }
14526
14527 void
14528 MoveNowEvent()
14529 {
14530     ChessProgramState *cps;
14531
14532     switch (gameMode) {
14533       case MachinePlaysWhite:
14534         if (!WhiteOnMove(forwardMostMove)) {
14535             DisplayError(_("It is your turn"), 0);
14536             return;
14537         }
14538         cps = &first;
14539         break;
14540       case MachinePlaysBlack:
14541         if (WhiteOnMove(forwardMostMove)) {
14542             DisplayError(_("It is your turn"), 0);
14543             return;
14544         }
14545         cps = &first;
14546         break;
14547       case TwoMachinesPlay:
14548         if (WhiteOnMove(forwardMostMove) ==
14549             (first.twoMachinesColor[0] == 'w')) {
14550             cps = &first;
14551         } else {
14552             cps = &second;
14553         }
14554         break;
14555       case BeginningOfGame:
14556       default:
14557         return;
14558     }
14559     SendToProgram("?\n", cps);
14560 }
14561
14562 void
14563 TruncateGameEvent()
14564 {
14565     EditGameEvent();
14566     if (gameMode != EditGame) return;
14567     TruncateGame();
14568 }
14569
14570 void
14571 TruncateGame()
14572 {
14573     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14574     if (forwardMostMove > currentMove) {
14575         if (gameInfo.resultDetails != NULL) {
14576             free(gameInfo.resultDetails);
14577             gameInfo.resultDetails = NULL;
14578             gameInfo.result = GameUnfinished;
14579         }
14580         forwardMostMove = currentMove;
14581         HistorySet(parseList, backwardMostMove, forwardMostMove,
14582                    currentMove-1);
14583     }
14584 }
14585
14586 void
14587 HintEvent()
14588 {
14589     if (appData.noChessProgram) return;
14590     switch (gameMode) {
14591       case MachinePlaysWhite:
14592         if (WhiteOnMove(forwardMostMove)) {
14593             DisplayError(_("Wait until your turn"), 0);
14594             return;
14595         }
14596         break;
14597       case BeginningOfGame:
14598       case MachinePlaysBlack:
14599         if (!WhiteOnMove(forwardMostMove)) {
14600             DisplayError(_("Wait until your turn"), 0);
14601             return;
14602         }
14603         break;
14604       default:
14605         DisplayError(_("No hint available"), 0);
14606         return;
14607     }
14608     SendToProgram("hint\n", &first);
14609     hintRequested = TRUE;
14610 }
14611
14612 void
14613 BookEvent()
14614 {
14615     if (appData.noChessProgram) return;
14616     switch (gameMode) {
14617       case MachinePlaysWhite:
14618         if (WhiteOnMove(forwardMostMove)) {
14619             DisplayError(_("Wait until your turn"), 0);
14620             return;
14621         }
14622         break;
14623       case BeginningOfGame:
14624       case MachinePlaysBlack:
14625         if (!WhiteOnMove(forwardMostMove)) {
14626             DisplayError(_("Wait until your turn"), 0);
14627             return;
14628         }
14629         break;
14630       case EditPosition:
14631         EditPositionDone(TRUE);
14632         break;
14633       case TwoMachinesPlay:
14634         return;
14635       default:
14636         break;
14637     }
14638     SendToProgram("bk\n", &first);
14639     bookOutput[0] = NULLCHAR;
14640     bookRequested = TRUE;
14641 }
14642
14643 void
14644 AboutGameEvent()
14645 {
14646     char *tags = PGNTags(&gameInfo);
14647     TagsPopUp(tags, CmailMsg());
14648     free(tags);
14649 }
14650
14651 /* end button procedures */
14652
14653 void
14654 PrintPosition(fp, move)
14655      FILE *fp;
14656      int move;
14657 {
14658     int i, j;
14659
14660     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14661         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14662             char c = PieceToChar(boards[move][i][j]);
14663             fputc(c == 'x' ? '.' : c, fp);
14664             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14665         }
14666     }
14667     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14668       fprintf(fp, "white to play\n");
14669     else
14670       fprintf(fp, "black to play\n");
14671 }
14672
14673 void
14674 PrintOpponents(fp)
14675      FILE *fp;
14676 {
14677     if (gameInfo.white != NULL) {
14678         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14679     } else {
14680         fprintf(fp, "\n");
14681     }
14682 }
14683
14684 /* Find last component of program's own name, using some heuristics */
14685 void
14686 TidyProgramName(prog, host, buf)
14687      char *prog, *host, buf[MSG_SIZ];
14688 {
14689     char *p, *q;
14690     int local = (strcmp(host, "localhost") == 0);
14691     while (!local && (p = strchr(prog, ';')) != NULL) {
14692         p++;
14693         while (*p == ' ') p++;
14694         prog = p;
14695     }
14696     if (*prog == '"' || *prog == '\'') {
14697         q = strchr(prog + 1, *prog);
14698     } else {
14699         q = strchr(prog, ' ');
14700     }
14701     if (q == NULL) q = prog + strlen(prog);
14702     p = q;
14703     while (p >= prog && *p != '/' && *p != '\\') p--;
14704     p++;
14705     if(p == prog && *p == '"') p++;
14706     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14707     memcpy(buf, p, q - p);
14708     buf[q - p] = NULLCHAR;
14709     if (!local) {
14710         strcat(buf, "@");
14711         strcat(buf, host);
14712     }
14713 }
14714
14715 char *
14716 TimeControlTagValue()
14717 {
14718     char buf[MSG_SIZ];
14719     if (!appData.clockMode) {
14720       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14721     } else if (movesPerSession > 0) {
14722       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14723     } else if (timeIncrement == 0) {
14724       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14725     } else {
14726       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14727     }
14728     return StrSave(buf);
14729 }
14730
14731 void
14732 SetGameInfo()
14733 {
14734     /* This routine is used only for certain modes */
14735     VariantClass v = gameInfo.variant;
14736     ChessMove r = GameUnfinished;
14737     char *p = NULL;
14738
14739     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14740         r = gameInfo.result;
14741         p = gameInfo.resultDetails;
14742         gameInfo.resultDetails = NULL;
14743     }
14744     ClearGameInfo(&gameInfo);
14745     gameInfo.variant = v;
14746
14747     switch (gameMode) {
14748       case MachinePlaysWhite:
14749         gameInfo.event = StrSave( appData.pgnEventHeader );
14750         gameInfo.site = StrSave(HostName());
14751         gameInfo.date = PGNDate();
14752         gameInfo.round = StrSave("-");
14753         gameInfo.white = StrSave(first.tidy);
14754         gameInfo.black = StrSave(UserName());
14755         gameInfo.timeControl = TimeControlTagValue();
14756         break;
14757
14758       case MachinePlaysBlack:
14759         gameInfo.event = StrSave( appData.pgnEventHeader );
14760         gameInfo.site = StrSave(HostName());
14761         gameInfo.date = PGNDate();
14762         gameInfo.round = StrSave("-");
14763         gameInfo.white = StrSave(UserName());
14764         gameInfo.black = StrSave(first.tidy);
14765         gameInfo.timeControl = TimeControlTagValue();
14766         break;
14767
14768       case TwoMachinesPlay:
14769         gameInfo.event = StrSave( appData.pgnEventHeader );
14770         gameInfo.site = StrSave(HostName());
14771         gameInfo.date = PGNDate();
14772         if (roundNr > 0) {
14773             char buf[MSG_SIZ];
14774             snprintf(buf, MSG_SIZ, "%d", roundNr);
14775             gameInfo.round = StrSave(buf);
14776         } else {
14777             gameInfo.round = StrSave("-");
14778         }
14779         if (first.twoMachinesColor[0] == 'w') {
14780             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14781             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14782         } else {
14783             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14784             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14785         }
14786         gameInfo.timeControl = TimeControlTagValue();
14787         break;
14788
14789       case EditGame:
14790         gameInfo.event = StrSave("Edited game");
14791         gameInfo.site = StrSave(HostName());
14792         gameInfo.date = PGNDate();
14793         gameInfo.round = StrSave("-");
14794         gameInfo.white = StrSave("-");
14795         gameInfo.black = StrSave("-");
14796         gameInfo.result = r;
14797         gameInfo.resultDetails = p;
14798         break;
14799
14800       case EditPosition:
14801         gameInfo.event = StrSave("Edited position");
14802         gameInfo.site = StrSave(HostName());
14803         gameInfo.date = PGNDate();
14804         gameInfo.round = StrSave("-");
14805         gameInfo.white = StrSave("-");
14806         gameInfo.black = StrSave("-");
14807         break;
14808
14809       case IcsPlayingWhite:
14810       case IcsPlayingBlack:
14811       case IcsObserving:
14812       case IcsExamining:
14813         break;
14814
14815       case PlayFromGameFile:
14816         gameInfo.event = StrSave("Game from non-PGN file");
14817         gameInfo.site = StrSave(HostName());
14818         gameInfo.date = PGNDate();
14819         gameInfo.round = StrSave("-");
14820         gameInfo.white = StrSave("?");
14821         gameInfo.black = StrSave("?");
14822         break;
14823
14824       default:
14825         break;
14826     }
14827 }
14828
14829 void
14830 ReplaceComment(index, text)
14831      int index;
14832      char *text;
14833 {
14834     int len;
14835     char *p;
14836     float score;
14837
14838     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14839        pvInfoList[index-1].depth == len &&
14840        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14841        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14842     while (*text == '\n') text++;
14843     len = strlen(text);
14844     while (len > 0 && text[len - 1] == '\n') len--;
14845
14846     if (commentList[index] != NULL)
14847       free(commentList[index]);
14848
14849     if (len == 0) {
14850         commentList[index] = NULL;
14851         return;
14852     }
14853   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14854       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14855       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14856     commentList[index] = (char *) malloc(len + 2);
14857     strncpy(commentList[index], text, len);
14858     commentList[index][len] = '\n';
14859     commentList[index][len + 1] = NULLCHAR;
14860   } else {
14861     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14862     char *p;
14863     commentList[index] = (char *) malloc(len + 7);
14864     safeStrCpy(commentList[index], "{\n", 3);
14865     safeStrCpy(commentList[index]+2, text, len+1);
14866     commentList[index][len+2] = NULLCHAR;
14867     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14868     strcat(commentList[index], "\n}\n");
14869   }
14870 }
14871
14872 void
14873 CrushCRs(text)
14874      char *text;
14875 {
14876   char *p = text;
14877   char *q = text;
14878   char ch;
14879
14880   do {
14881     ch = *p++;
14882     if (ch == '\r') continue;
14883     *q++ = ch;
14884   } while (ch != '\0');
14885 }
14886
14887 void
14888 AppendComment(index, text, addBraces)
14889      int index;
14890      char *text;
14891      Boolean addBraces; // [HGM] braces: tells if we should add {}
14892 {
14893     int oldlen, len;
14894     char *old;
14895
14896 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14897     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14898
14899     CrushCRs(text);
14900     while (*text == '\n') text++;
14901     len = strlen(text);
14902     while (len > 0 && text[len - 1] == '\n') len--;
14903
14904     if (len == 0) return;
14905
14906     if (commentList[index] != NULL) {
14907       Boolean addClosingBrace = addBraces;
14908         old = commentList[index];
14909         oldlen = strlen(old);
14910         while(commentList[index][oldlen-1] ==  '\n')
14911           commentList[index][--oldlen] = NULLCHAR;
14912         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14913         safeStrCpy(commentList[index], old, oldlen + len + 6);
14914         free(old);
14915         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14916         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14917           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14918           while (*text == '\n') { text++; len--; }
14919           commentList[index][--oldlen] = NULLCHAR;
14920       }
14921         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14922         else          strcat(commentList[index], "\n");
14923         strcat(commentList[index], text);
14924         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14925         else          strcat(commentList[index], "\n");
14926     } else {
14927         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14928         if(addBraces)
14929           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14930         else commentList[index][0] = NULLCHAR;
14931         strcat(commentList[index], text);
14932         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14933         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14934     }
14935 }
14936
14937 static char * FindStr( char * text, char * sub_text )
14938 {
14939     char * result = strstr( text, sub_text );
14940
14941     if( result != NULL ) {
14942         result += strlen( sub_text );
14943     }
14944
14945     return result;
14946 }
14947
14948 /* [AS] Try to extract PV info from PGN comment */
14949 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14950 char *GetInfoFromComment( int index, char * text )
14951 {
14952     char * sep = text, *p;
14953
14954     if( text != NULL && index > 0 ) {
14955         int score = 0;
14956         int depth = 0;
14957         int time = -1, sec = 0, deci;
14958         char * s_eval = FindStr( text, "[%eval " );
14959         char * s_emt = FindStr( text, "[%emt " );
14960
14961         if( s_eval != NULL || s_emt != NULL ) {
14962             /* New style */
14963             char delim;
14964
14965             if( s_eval != NULL ) {
14966                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14967                     return text;
14968                 }
14969
14970                 if( delim != ']' ) {
14971                     return text;
14972                 }
14973             }
14974
14975             if( s_emt != NULL ) {
14976             }
14977                 return text;
14978         }
14979         else {
14980             /* We expect something like: [+|-]nnn.nn/dd */
14981             int score_lo = 0;
14982
14983             if(*text != '{') return text; // [HGM] braces: must be normal comment
14984
14985             sep = strchr( text, '/' );
14986             if( sep == NULL || sep < (text+4) ) {
14987                 return text;
14988             }
14989
14990             p = text;
14991             if(p[1] == '(') { // comment starts with PV
14992                p = strchr(p, ')'); // locate end of PV
14993                if(p == NULL || sep < p+5) return text;
14994                // at this point we have something like "{(.*) +0.23/6 ..."
14995                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14996                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14997                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14998             }
14999             time = -1; sec = -1; deci = -1;
15000             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15001                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15002                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15003                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15004                 return text;
15005             }
15006
15007             if( score_lo < 0 || score_lo >= 100 ) {
15008                 return text;
15009             }
15010
15011             if(sec >= 0) time = 600*time + 10*sec; else
15012             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15013
15014             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15015
15016             /* [HGM] PV time: now locate end of PV info */
15017             while( *++sep >= '0' && *sep <= '9'); // strip depth
15018             if(time >= 0)
15019             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15020             if(sec >= 0)
15021             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15022             if(deci >= 0)
15023             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15024             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15025         }
15026
15027         if( depth <= 0 ) {
15028             return text;
15029         }
15030
15031         if( time < 0 ) {
15032             time = -1;
15033         }
15034
15035         pvInfoList[index-1].depth = depth;
15036         pvInfoList[index-1].score = score;
15037         pvInfoList[index-1].time  = 10*time; // centi-sec
15038         if(*sep == '}') *sep = 0; else *--sep = '{';
15039         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15040     }
15041     return sep;
15042 }
15043
15044 void
15045 SendToProgram(message, cps)
15046      char *message;
15047      ChessProgramState *cps;
15048 {
15049     int count, outCount, error;
15050     char buf[MSG_SIZ];
15051
15052     if (cps->pr == NoProc) return;
15053     Attention(cps);
15054
15055     if (appData.debugMode) {
15056         TimeMark now;
15057         GetTimeMark(&now);
15058         fprintf(debugFP, "%ld >%-6s: %s",
15059                 SubtractTimeMarks(&now, &programStartTime),
15060                 cps->which, message);
15061     }
15062
15063     count = strlen(message);
15064     outCount = OutputToProcess(cps->pr, message, count, &error);
15065     if (outCount < count && !exiting
15066                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15067       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15068       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15069         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15070             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15071                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15072                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15073                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15074             } else {
15075                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15076                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15077                 gameInfo.result = res;
15078             }
15079             gameInfo.resultDetails = StrSave(buf);
15080         }
15081         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15082         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15083     }
15084 }
15085
15086 void
15087 ReceiveFromProgram(isr, closure, message, count, error)
15088      InputSourceRef isr;
15089      VOIDSTAR closure;
15090      char *message;
15091      int count;
15092      int error;
15093 {
15094     char *end_str;
15095     char buf[MSG_SIZ];
15096     ChessProgramState *cps = (ChessProgramState *)closure;
15097
15098     if (isr != cps->isr) return; /* Killed intentionally */
15099     if (count <= 0) {
15100         if (count == 0) {
15101             RemoveInputSource(cps->isr);
15102             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15103             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15104                     _(cps->which), cps->program);
15105         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15106                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15107                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15108                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15109                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15110                 } else {
15111                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15112                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15113                     gameInfo.result = res;
15114                 }
15115                 gameInfo.resultDetails = StrSave(buf);
15116             }
15117             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15118             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15119         } else {
15120             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15121                     _(cps->which), cps->program);
15122             RemoveInputSource(cps->isr);
15123
15124             /* [AS] Program is misbehaving badly... kill it */
15125             if( count == -2 ) {
15126                 DestroyChildProcess( cps->pr, 9 );
15127                 cps->pr = NoProc;
15128             }
15129
15130             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15131         }
15132         return;
15133     }
15134
15135     if ((end_str = strchr(message, '\r')) != NULL)
15136       *end_str = NULLCHAR;
15137     if ((end_str = strchr(message, '\n')) != NULL)
15138       *end_str = NULLCHAR;
15139
15140     if (appData.debugMode) {
15141         TimeMark now; int print = 1;
15142         char *quote = ""; char c; int i;
15143
15144         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15145                 char start = message[0];
15146                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15147                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15148                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15149                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15150                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15151                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15152                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15153                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15154                    sscanf(message, "hint: %c", &c)!=1 && 
15155                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15156                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15157                     print = (appData.engineComments >= 2);
15158                 }
15159                 message[0] = start; // restore original message
15160         }
15161         if(print) {
15162                 GetTimeMark(&now);
15163                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15164                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15165                         quote,
15166                         message);
15167         }
15168     }
15169
15170     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15171     if (appData.icsEngineAnalyze) {
15172         if (strstr(message, "whisper") != NULL ||
15173              strstr(message, "kibitz") != NULL ||
15174             strstr(message, "tellics") != NULL) return;
15175     }
15176
15177     HandleMachineMove(message, cps);
15178 }
15179
15180
15181 void
15182 SendTimeControl(cps, mps, tc, inc, sd, st)
15183      ChessProgramState *cps;
15184      int mps, inc, sd, st;
15185      long tc;
15186 {
15187     char buf[MSG_SIZ];
15188     int seconds;
15189
15190     if( timeControl_2 > 0 ) {
15191         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15192             tc = timeControl_2;
15193         }
15194     }
15195     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15196     inc /= cps->timeOdds;
15197     st  /= cps->timeOdds;
15198
15199     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15200
15201     if (st > 0) {
15202       /* Set exact time per move, normally using st command */
15203       if (cps->stKludge) {
15204         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15205         seconds = st % 60;
15206         if (seconds == 0) {
15207           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15208         } else {
15209           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15210         }
15211       } else {
15212         snprintf(buf, MSG_SIZ, "st %d\n", st);
15213       }
15214     } else {
15215       /* Set conventional or incremental time control, using level command */
15216       if (seconds == 0) {
15217         /* Note old gnuchess bug -- minutes:seconds used to not work.
15218            Fixed in later versions, but still avoid :seconds
15219            when seconds is 0. */
15220         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15221       } else {
15222         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15223                  seconds, inc/1000.);
15224       }
15225     }
15226     SendToProgram(buf, cps);
15227
15228     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15229     /* Orthogonally, limit search to given depth */
15230     if (sd > 0) {
15231       if (cps->sdKludge) {
15232         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15233       } else {
15234         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15235       }
15236       SendToProgram(buf, cps);
15237     }
15238
15239     if(cps->nps >= 0) { /* [HGM] nps */
15240         if(cps->supportsNPS == FALSE)
15241           cps->nps = -1; // don't use if engine explicitly says not supported!
15242         else {
15243           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15244           SendToProgram(buf, cps);
15245         }
15246     }
15247 }
15248
15249 ChessProgramState *WhitePlayer()
15250 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15251 {
15252     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15253        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15254         return &second;
15255     return &first;
15256 }
15257
15258 void
15259 SendTimeRemaining(cps, machineWhite)
15260      ChessProgramState *cps;
15261      int /*boolean*/ machineWhite;
15262 {
15263     char message[MSG_SIZ];
15264     long time, otime;
15265
15266     /* Note: this routine must be called when the clocks are stopped
15267        or when they have *just* been set or switched; otherwise
15268        it will be off by the time since the current tick started.
15269     */
15270     if (machineWhite) {
15271         time = whiteTimeRemaining / 10;
15272         otime = blackTimeRemaining / 10;
15273     } else {
15274         time = blackTimeRemaining / 10;
15275         otime = whiteTimeRemaining / 10;
15276     }
15277     /* [HGM] translate opponent's time by time-odds factor */
15278     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15279     if (appData.debugMode) {
15280         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15281     }
15282
15283     if (time <= 0) time = 1;
15284     if (otime <= 0) otime = 1;
15285
15286     snprintf(message, MSG_SIZ, "time %ld\n", time);
15287     SendToProgram(message, cps);
15288
15289     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15290     SendToProgram(message, cps);
15291 }
15292
15293 int
15294 BoolFeature(p, name, loc, cps)
15295      char **p;
15296      char *name;
15297      int *loc;
15298      ChessProgramState *cps;
15299 {
15300   char buf[MSG_SIZ];
15301   int len = strlen(name);
15302   int val;
15303
15304   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15305     (*p) += len + 1;
15306     sscanf(*p, "%d", &val);
15307     *loc = (val != 0);
15308     while (**p && **p != ' ')
15309       (*p)++;
15310     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15311     SendToProgram(buf, cps);
15312     return TRUE;
15313   }
15314   return FALSE;
15315 }
15316
15317 int
15318 IntFeature(p, name, loc, cps)
15319      char **p;
15320      char *name;
15321      int *loc;
15322      ChessProgramState *cps;
15323 {
15324   char buf[MSG_SIZ];
15325   int len = strlen(name);
15326   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15327     (*p) += len + 1;
15328     sscanf(*p, "%d", loc);
15329     while (**p && **p != ' ') (*p)++;
15330     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15331     SendToProgram(buf, cps);
15332     return TRUE;
15333   }
15334   return FALSE;
15335 }
15336
15337 int
15338 StringFeature(p, name, loc, cps)
15339      char **p;
15340      char *name;
15341      char loc[];
15342      ChessProgramState *cps;
15343 {
15344   char buf[MSG_SIZ];
15345   int len = strlen(name);
15346   if (strncmp((*p), name, len) == 0
15347       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15348     (*p) += len + 2;
15349     sscanf(*p, "%[^\"]", loc);
15350     while (**p && **p != '\"') (*p)++;
15351     if (**p == '\"') (*p)++;
15352     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15353     SendToProgram(buf, cps);
15354     return TRUE;
15355   }
15356   return FALSE;
15357 }
15358
15359 int
15360 ParseOption(Option *opt, ChessProgramState *cps)
15361 // [HGM] options: process the string that defines an engine option, and determine
15362 // name, type, default value, and allowed value range
15363 {
15364         char *p, *q, buf[MSG_SIZ];
15365         int n, min = (-1)<<31, max = 1<<31, def;
15366
15367         if(p = strstr(opt->name, " -spin ")) {
15368             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15369             if(max < min) max = min; // enforce consistency
15370             if(def < min) def = min;
15371             if(def > max) def = max;
15372             opt->value = def;
15373             opt->min = min;
15374             opt->max = max;
15375             opt->type = Spin;
15376         } else if((p = strstr(opt->name, " -slider "))) {
15377             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15378             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15379             if(max < min) max = min; // enforce consistency
15380             if(def < min) def = min;
15381             if(def > max) def = max;
15382             opt->value = def;
15383             opt->min = min;
15384             opt->max = max;
15385             opt->type = Spin; // Slider;
15386         } else if((p = strstr(opt->name, " -string "))) {
15387             opt->textValue = p+9;
15388             opt->type = TextBox;
15389         } else if((p = strstr(opt->name, " -file "))) {
15390             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15391             opt->textValue = p+7;
15392             opt->type = FileName; // FileName;
15393         } else if((p = strstr(opt->name, " -path "))) {
15394             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15395             opt->textValue = p+7;
15396             opt->type = PathName; // PathName;
15397         } else if(p = strstr(opt->name, " -check ")) {
15398             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15399             opt->value = (def != 0);
15400             opt->type = CheckBox;
15401         } else if(p = strstr(opt->name, " -combo ")) {
15402             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15403             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15404             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15405             opt->value = n = 0;
15406             while(q = StrStr(q, " /// ")) {
15407                 n++; *q = 0;    // count choices, and null-terminate each of them
15408                 q += 5;
15409                 if(*q == '*') { // remember default, which is marked with * prefix
15410                     q++;
15411                     opt->value = n;
15412                 }
15413                 cps->comboList[cps->comboCnt++] = q;
15414             }
15415             cps->comboList[cps->comboCnt++] = NULL;
15416             opt->max = n + 1;
15417             opt->type = ComboBox;
15418         } else if(p = strstr(opt->name, " -button")) {
15419             opt->type = Button;
15420         } else if(p = strstr(opt->name, " -save")) {
15421             opt->type = SaveButton;
15422         } else return FALSE;
15423         *p = 0; // terminate option name
15424         // now look if the command-line options define a setting for this engine option.
15425         if(cps->optionSettings && cps->optionSettings[0])
15426             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15427         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15428           snprintf(buf, MSG_SIZ, "option %s", p);
15429                 if(p = strstr(buf, ",")) *p = 0;
15430                 if(q = strchr(buf, '=')) switch(opt->type) {
15431                     case ComboBox:
15432                         for(n=0; n<opt->max; n++)
15433                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15434                         break;
15435                     case TextBox:
15436                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15437                         break;
15438                     case Spin:
15439                     case CheckBox:
15440                         opt->value = atoi(q+1);
15441                     default:
15442                         break;
15443                 }
15444                 strcat(buf, "\n");
15445                 SendToProgram(buf, cps);
15446         }
15447         return TRUE;
15448 }
15449
15450 void
15451 FeatureDone(cps, val)
15452      ChessProgramState* cps;
15453      int val;
15454 {
15455   DelayedEventCallback cb = GetDelayedEvent();
15456   if ((cb == InitBackEnd3 && cps == &first) ||
15457       (cb == SettingsMenuIfReady && cps == &second) ||
15458       (cb == LoadEngine) ||
15459       (cb == TwoMachinesEventIfReady)) {
15460     CancelDelayedEvent();
15461     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15462   }
15463   cps->initDone = val;
15464 }
15465
15466 /* Parse feature command from engine */
15467 void
15468 ParseFeatures(args, cps)
15469      char* args;
15470      ChessProgramState *cps;
15471 {
15472   char *p = args;
15473   char *q;
15474   int val;
15475   char buf[MSG_SIZ];
15476
15477   for (;;) {
15478     while (*p == ' ') p++;
15479     if (*p == NULLCHAR) return;
15480
15481     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15482     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15483     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15484     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15485     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15486     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15487     if (BoolFeature(&p, "reuse", &val, cps)) {
15488       /* Engine can disable reuse, but can't enable it if user said no */
15489       if (!val) cps->reuse = FALSE;
15490       continue;
15491     }
15492     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15493     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15494       if (gameMode == TwoMachinesPlay) {
15495         DisplayTwoMachinesTitle();
15496       } else {
15497         DisplayTitle("");
15498       }
15499       continue;
15500     }
15501     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15502     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15503     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15504     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15505     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15506     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15507     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15508     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15509     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15510     if (IntFeature(&p, "done", &val, cps)) {
15511       FeatureDone(cps, val);
15512       continue;
15513     }
15514     /* Added by Tord: */
15515     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15516     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15517     /* End of additions by Tord */
15518
15519     /* [HGM] added features: */
15520     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15521     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15522     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15523     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15524     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15525     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15526     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15527         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15528           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15529             SendToProgram(buf, cps);
15530             continue;
15531         }
15532         if(cps->nrOptions >= MAX_OPTIONS) {
15533             cps->nrOptions--;
15534             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15535             DisplayError(buf, 0);
15536         }
15537         continue;
15538     }
15539     /* End of additions by HGM */
15540
15541     /* unknown feature: complain and skip */
15542     q = p;
15543     while (*q && *q != '=') q++;
15544     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15545     SendToProgram(buf, cps);
15546     p = q;
15547     if (*p == '=') {
15548       p++;
15549       if (*p == '\"') {
15550         p++;
15551         while (*p && *p != '\"') p++;
15552         if (*p == '\"') p++;
15553       } else {
15554         while (*p && *p != ' ') p++;
15555       }
15556     }
15557   }
15558
15559 }
15560
15561 void
15562 PeriodicUpdatesEvent(newState)
15563      int newState;
15564 {
15565     if (newState == appData.periodicUpdates)
15566       return;
15567
15568     appData.periodicUpdates=newState;
15569
15570     /* Display type changes, so update it now */
15571 //    DisplayAnalysis();
15572
15573     /* Get the ball rolling again... */
15574     if (newState) {
15575         AnalysisPeriodicEvent(1);
15576         StartAnalysisClock();
15577     }
15578 }
15579
15580 void
15581 PonderNextMoveEvent(newState)
15582      int newState;
15583 {
15584     if (newState == appData.ponderNextMove) return;
15585     if (gameMode == EditPosition) EditPositionDone(TRUE);
15586     if (newState) {
15587         SendToProgram("hard\n", &first);
15588         if (gameMode == TwoMachinesPlay) {
15589             SendToProgram("hard\n", &second);
15590         }
15591     } else {
15592         SendToProgram("easy\n", &first);
15593         thinkOutput[0] = NULLCHAR;
15594         if (gameMode == TwoMachinesPlay) {
15595             SendToProgram("easy\n", &second);
15596         }
15597     }
15598     appData.ponderNextMove = newState;
15599 }
15600
15601 void
15602 NewSettingEvent(option, feature, command, value)
15603      char *command;
15604      int option, value, *feature;
15605 {
15606     char buf[MSG_SIZ];
15607
15608     if (gameMode == EditPosition) EditPositionDone(TRUE);
15609     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15610     if(feature == NULL || *feature) SendToProgram(buf, &first);
15611     if (gameMode == TwoMachinesPlay) {
15612         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15613     }
15614 }
15615
15616 void
15617 ShowThinkingEvent()
15618 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15619 {
15620     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15621     int newState = appData.showThinking
15622         // [HGM] thinking: other features now need thinking output as well
15623         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15624
15625     if (oldState == newState) return;
15626     oldState = newState;
15627     if (gameMode == EditPosition) EditPositionDone(TRUE);
15628     if (oldState) {
15629         SendToProgram("post\n", &first);
15630         if (gameMode == TwoMachinesPlay) {
15631             SendToProgram("post\n", &second);
15632         }
15633     } else {
15634         SendToProgram("nopost\n", &first);
15635         thinkOutput[0] = NULLCHAR;
15636         if (gameMode == TwoMachinesPlay) {
15637             SendToProgram("nopost\n", &second);
15638         }
15639     }
15640 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15641 }
15642
15643 void
15644 AskQuestionEvent(title, question, replyPrefix, which)
15645      char *title; char *question; char *replyPrefix; char *which;
15646 {
15647   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15648   if (pr == NoProc) return;
15649   AskQuestion(title, question, replyPrefix, pr);
15650 }
15651
15652 void
15653 TypeInEvent(char firstChar)
15654 {
15655     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15656         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15657         gameMode == AnalyzeMode || gameMode == EditGame || 
15658         gameMode == EditPosition || gameMode == IcsExamining ||
15659         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15660         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15661                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15662                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15663         gameMode == Training) PopUpMoveDialog(firstChar);
15664 }
15665
15666 void
15667 TypeInDoneEvent(char *move)
15668 {
15669         Board board;
15670         int n, fromX, fromY, toX, toY;
15671         char promoChar;
15672         ChessMove moveType;
15673
15674         // [HGM] FENedit
15675         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15676                 EditPositionPasteFEN(move);
15677                 return;
15678         }
15679         // [HGM] movenum: allow move number to be typed in any mode
15680         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15681           ToNrEvent(2*n-1);
15682           return;
15683         }
15684
15685       if (gameMode != EditGame && currentMove != forwardMostMove && 
15686         gameMode != Training) {
15687         DisplayMoveError(_("Displayed move is not current"));
15688       } else {
15689         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15690           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15691         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15692         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15693           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15694           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15695         } else {
15696           DisplayMoveError(_("Could not parse move"));
15697         }
15698       }
15699 }
15700
15701 void
15702 DisplayMove(moveNumber)
15703      int moveNumber;
15704 {
15705     char message[MSG_SIZ];
15706     char res[MSG_SIZ];
15707     char cpThinkOutput[MSG_SIZ];
15708
15709     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15710
15711     if (moveNumber == forwardMostMove - 1 ||
15712         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15713
15714         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15715
15716         if (strchr(cpThinkOutput, '\n')) {
15717             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15718         }
15719     } else {
15720         *cpThinkOutput = NULLCHAR;
15721     }
15722
15723     /* [AS] Hide thinking from human user */
15724     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15725         *cpThinkOutput = NULLCHAR;
15726         if( thinkOutput[0] != NULLCHAR ) {
15727             int i;
15728
15729             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15730                 cpThinkOutput[i] = '.';
15731             }
15732             cpThinkOutput[i] = NULLCHAR;
15733             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15734         }
15735     }
15736
15737     if (moveNumber == forwardMostMove - 1 &&
15738         gameInfo.resultDetails != NULL) {
15739         if (gameInfo.resultDetails[0] == NULLCHAR) {
15740           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15741         } else {
15742           snprintf(res, MSG_SIZ, " {%s} %s",
15743                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15744         }
15745     } else {
15746         res[0] = NULLCHAR;
15747     }
15748
15749     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15750         DisplayMessage(res, cpThinkOutput);
15751     } else {
15752       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15753                 WhiteOnMove(moveNumber) ? " " : ".. ",
15754                 parseList[moveNumber], res);
15755         DisplayMessage(message, cpThinkOutput);
15756     }
15757 }
15758
15759 void
15760 DisplayComment(moveNumber, text)
15761      int moveNumber;
15762      char *text;
15763 {
15764     char title[MSG_SIZ];
15765
15766     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15767       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15768     } else {
15769       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15770               WhiteOnMove(moveNumber) ? " " : ".. ",
15771               parseList[moveNumber]);
15772     }
15773     if (text != NULL && (appData.autoDisplayComment || commentUp))
15774         CommentPopUp(title, text);
15775 }
15776
15777 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15778  * might be busy thinking or pondering.  It can be omitted if your
15779  * gnuchess is configured to stop thinking immediately on any user
15780  * input.  However, that gnuchess feature depends on the FIONREAD
15781  * ioctl, which does not work properly on some flavors of Unix.
15782  */
15783 void
15784 Attention(cps)
15785      ChessProgramState *cps;
15786 {
15787 #if ATTENTION
15788     if (!cps->useSigint) return;
15789     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15790     switch (gameMode) {
15791       case MachinePlaysWhite:
15792       case MachinePlaysBlack:
15793       case TwoMachinesPlay:
15794       case IcsPlayingWhite:
15795       case IcsPlayingBlack:
15796       case AnalyzeMode:
15797       case AnalyzeFile:
15798         /* Skip if we know it isn't thinking */
15799         if (!cps->maybeThinking) return;
15800         if (appData.debugMode)
15801           fprintf(debugFP, "Interrupting %s\n", cps->which);
15802         InterruptChildProcess(cps->pr);
15803         cps->maybeThinking = FALSE;
15804         break;
15805       default:
15806         break;
15807     }
15808 #endif /*ATTENTION*/
15809 }
15810
15811 int
15812 CheckFlags()
15813 {
15814     if (whiteTimeRemaining <= 0) {
15815         if (!whiteFlag) {
15816             whiteFlag = TRUE;
15817             if (appData.icsActive) {
15818                 if (appData.autoCallFlag &&
15819                     gameMode == IcsPlayingBlack && !blackFlag) {
15820                   SendToICS(ics_prefix);
15821                   SendToICS("flag\n");
15822                 }
15823             } else {
15824                 if (blackFlag) {
15825                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15826                 } else {
15827                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15828                     if (appData.autoCallFlag) {
15829                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15830                         return TRUE;
15831                     }
15832                 }
15833             }
15834         }
15835     }
15836     if (blackTimeRemaining <= 0) {
15837         if (!blackFlag) {
15838             blackFlag = TRUE;
15839             if (appData.icsActive) {
15840                 if (appData.autoCallFlag &&
15841                     gameMode == IcsPlayingWhite && !whiteFlag) {
15842                   SendToICS(ics_prefix);
15843                   SendToICS("flag\n");
15844                 }
15845             } else {
15846                 if (whiteFlag) {
15847                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15848                 } else {
15849                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15850                     if (appData.autoCallFlag) {
15851                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15852                         return TRUE;
15853                     }
15854                 }
15855             }
15856         }
15857     }
15858     return FALSE;
15859 }
15860
15861 void
15862 CheckTimeControl()
15863 {
15864     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15865         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15866
15867     /*
15868      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15869      */
15870     if ( !WhiteOnMove(forwardMostMove) ) {
15871         /* White made time control */
15872         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15873         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15874         /* [HGM] time odds: correct new time quota for time odds! */
15875                                             / WhitePlayer()->timeOdds;
15876         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15877     } else {
15878         lastBlack -= blackTimeRemaining;
15879         /* Black made time control */
15880         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15881                                             / WhitePlayer()->other->timeOdds;
15882         lastWhite = whiteTimeRemaining;
15883     }
15884 }
15885
15886 void
15887 DisplayBothClocks()
15888 {
15889     int wom = gameMode == EditPosition ?
15890       !blackPlaysFirst : WhiteOnMove(currentMove);
15891     DisplayWhiteClock(whiteTimeRemaining, wom);
15892     DisplayBlackClock(blackTimeRemaining, !wom);
15893 }
15894
15895
15896 /* Timekeeping seems to be a portability nightmare.  I think everyone
15897    has ftime(), but I'm really not sure, so I'm including some ifdefs
15898    to use other calls if you don't.  Clocks will be less accurate if
15899    you have neither ftime nor gettimeofday.
15900 */
15901
15902 /* VS 2008 requires the #include outside of the function */
15903 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15904 #include <sys/timeb.h>
15905 #endif
15906
15907 /* Get the current time as a TimeMark */
15908 void
15909 GetTimeMark(tm)
15910      TimeMark *tm;
15911 {
15912 #if HAVE_GETTIMEOFDAY
15913
15914     struct timeval timeVal;
15915     struct timezone timeZone;
15916
15917     gettimeofday(&timeVal, &timeZone);
15918     tm->sec = (long) timeVal.tv_sec;
15919     tm->ms = (int) (timeVal.tv_usec / 1000L);
15920
15921 #else /*!HAVE_GETTIMEOFDAY*/
15922 #if HAVE_FTIME
15923
15924 // include <sys/timeb.h> / moved to just above start of function
15925     struct timeb timeB;
15926
15927     ftime(&timeB);
15928     tm->sec = (long) timeB.time;
15929     tm->ms = (int) timeB.millitm;
15930
15931 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15932     tm->sec = (long) time(NULL);
15933     tm->ms = 0;
15934 #endif
15935 #endif
15936 }
15937
15938 /* Return the difference in milliseconds between two
15939    time marks.  We assume the difference will fit in a long!
15940 */
15941 long
15942 SubtractTimeMarks(tm2, tm1)
15943      TimeMark *tm2, *tm1;
15944 {
15945     return 1000L*(tm2->sec - tm1->sec) +
15946            (long) (tm2->ms - tm1->ms);
15947 }
15948
15949
15950 /*
15951  * Code to manage the game clocks.
15952  *
15953  * In tournament play, black starts the clock and then white makes a move.
15954  * We give the human user a slight advantage if he is playing white---the
15955  * clocks don't run until he makes his first move, so it takes zero time.
15956  * Also, we don't account for network lag, so we could get out of sync
15957  * with GNU Chess's clock -- but then, referees are always right.
15958  */
15959
15960 static TimeMark tickStartTM;
15961 static long intendedTickLength;
15962
15963 long
15964 NextTickLength(timeRemaining)
15965      long timeRemaining;
15966 {
15967     long nominalTickLength, nextTickLength;
15968
15969     if (timeRemaining > 0L && timeRemaining <= 10000L)
15970       nominalTickLength = 100L;
15971     else
15972       nominalTickLength = 1000L;
15973     nextTickLength = timeRemaining % nominalTickLength;
15974     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15975
15976     return nextTickLength;
15977 }
15978
15979 /* Adjust clock one minute up or down */
15980 void
15981 AdjustClock(Boolean which, int dir)
15982 {
15983     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15984     if(which) blackTimeRemaining += 60000*dir;
15985     else      whiteTimeRemaining += 60000*dir;
15986     DisplayBothClocks();
15987     adjustedClock = TRUE;
15988 }
15989
15990 /* Stop clocks and reset to a fresh time control */
15991 void
15992 ResetClocks()
15993 {
15994     (void) StopClockTimer();
15995     if (appData.icsActive) {
15996         whiteTimeRemaining = blackTimeRemaining = 0;
15997     } else if (searchTime) {
15998         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15999         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16000     } else { /* [HGM] correct new time quote for time odds */
16001         whiteTC = blackTC = fullTimeControlString;
16002         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16003         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16004     }
16005     if (whiteFlag || blackFlag) {
16006         DisplayTitle("");
16007         whiteFlag = blackFlag = FALSE;
16008     }
16009     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16010     DisplayBothClocks();
16011     adjustedClock = FALSE;
16012 }
16013
16014 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16015
16016 /* Decrement running clock by amount of time that has passed */
16017 void
16018 DecrementClocks()
16019 {
16020     long timeRemaining;
16021     long lastTickLength, fudge;
16022     TimeMark now;
16023
16024     if (!appData.clockMode) return;
16025     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16026
16027     GetTimeMark(&now);
16028
16029     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16030
16031     /* Fudge if we woke up a little too soon */
16032     fudge = intendedTickLength - lastTickLength;
16033     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16034
16035     if (WhiteOnMove(forwardMostMove)) {
16036         if(whiteNPS >= 0) lastTickLength = 0;
16037         timeRemaining = whiteTimeRemaining -= lastTickLength;
16038         if(timeRemaining < 0 && !appData.icsActive) {
16039             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16040             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16041                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16042                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16043             }
16044         }
16045         DisplayWhiteClock(whiteTimeRemaining - fudge,
16046                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16047     } else {
16048         if(blackNPS >= 0) lastTickLength = 0;
16049         timeRemaining = blackTimeRemaining -= lastTickLength;
16050         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16051             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16052             if(suddenDeath) {
16053                 blackStartMove = forwardMostMove;
16054                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16055             }
16056         }
16057         DisplayBlackClock(blackTimeRemaining - fudge,
16058                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16059     }
16060     if (CheckFlags()) return;
16061
16062     tickStartTM = now;
16063     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16064     StartClockTimer(intendedTickLength);
16065
16066     /* if the time remaining has fallen below the alarm threshold, sound the
16067      * alarm. if the alarm has sounded and (due to a takeback or time control
16068      * with increment) the time remaining has increased to a level above the
16069      * threshold, reset the alarm so it can sound again.
16070      */
16071
16072     if (appData.icsActive && appData.icsAlarm) {
16073
16074         /* make sure we are dealing with the user's clock */
16075         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16076                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16077            )) return;
16078
16079         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16080             alarmSounded = FALSE;
16081         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16082             PlayAlarmSound();
16083             alarmSounded = TRUE;
16084         }
16085     }
16086 }
16087
16088
16089 /* A player has just moved, so stop the previously running
16090    clock and (if in clock mode) start the other one.
16091    We redisplay both clocks in case we're in ICS mode, because
16092    ICS gives us an update to both clocks after every move.
16093    Note that this routine is called *after* forwardMostMove
16094    is updated, so the last fractional tick must be subtracted
16095    from the color that is *not* on move now.
16096 */
16097 void
16098 SwitchClocks(int newMoveNr)
16099 {
16100     long lastTickLength;
16101     TimeMark now;
16102     int flagged = FALSE;
16103
16104     GetTimeMark(&now);
16105
16106     if (StopClockTimer() && appData.clockMode) {
16107         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16108         if (!WhiteOnMove(forwardMostMove)) {
16109             if(blackNPS >= 0) lastTickLength = 0;
16110             blackTimeRemaining -= lastTickLength;
16111            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16112 //         if(pvInfoList[forwardMostMove].time == -1)
16113                  pvInfoList[forwardMostMove].time =               // use GUI time
16114                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16115         } else {
16116            if(whiteNPS >= 0) lastTickLength = 0;
16117            whiteTimeRemaining -= lastTickLength;
16118            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16119 //         if(pvInfoList[forwardMostMove].time == -1)
16120                  pvInfoList[forwardMostMove].time =
16121                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16122         }
16123         flagged = CheckFlags();
16124     }
16125     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16126     CheckTimeControl();
16127
16128     if (flagged || !appData.clockMode) return;
16129
16130     switch (gameMode) {
16131       case MachinePlaysBlack:
16132       case MachinePlaysWhite:
16133       case BeginningOfGame:
16134         if (pausing) return;
16135         break;
16136
16137       case EditGame:
16138       case PlayFromGameFile:
16139       case IcsExamining:
16140         return;
16141
16142       default:
16143         break;
16144     }
16145
16146     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16147         if(WhiteOnMove(forwardMostMove))
16148              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16149         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16150     }
16151
16152     tickStartTM = now;
16153     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16154       whiteTimeRemaining : blackTimeRemaining);
16155     StartClockTimer(intendedTickLength);
16156 }
16157
16158
16159 /* Stop both clocks */
16160 void
16161 StopClocks()
16162 {
16163     long lastTickLength;
16164     TimeMark now;
16165
16166     if (!StopClockTimer()) return;
16167     if (!appData.clockMode) return;
16168
16169     GetTimeMark(&now);
16170
16171     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16172     if (WhiteOnMove(forwardMostMove)) {
16173         if(whiteNPS >= 0) lastTickLength = 0;
16174         whiteTimeRemaining -= lastTickLength;
16175         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16176     } else {
16177         if(blackNPS >= 0) lastTickLength = 0;
16178         blackTimeRemaining -= lastTickLength;
16179         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16180     }
16181     CheckFlags();
16182 }
16183
16184 /* Start clock of player on move.  Time may have been reset, so
16185    if clock is already running, stop and restart it. */
16186 void
16187 StartClocks()
16188 {
16189     (void) StopClockTimer(); /* in case it was running already */
16190     DisplayBothClocks();
16191     if (CheckFlags()) return;
16192
16193     if (!appData.clockMode) return;
16194     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16195
16196     GetTimeMark(&tickStartTM);
16197     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16198       whiteTimeRemaining : blackTimeRemaining);
16199
16200    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16201     whiteNPS = blackNPS = -1;
16202     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16203        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16204         whiteNPS = first.nps;
16205     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16206        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16207         blackNPS = first.nps;
16208     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16209         whiteNPS = second.nps;
16210     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16211         blackNPS = second.nps;
16212     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16213
16214     StartClockTimer(intendedTickLength);
16215 }
16216
16217 char *
16218 TimeString(ms)
16219      long ms;
16220 {
16221     long second, minute, hour, day;
16222     char *sign = "";
16223     static char buf[32];
16224
16225     if (ms > 0 && ms <= 9900) {
16226       /* convert milliseconds to tenths, rounding up */
16227       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16228
16229       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16230       return buf;
16231     }
16232
16233     /* convert milliseconds to seconds, rounding up */
16234     /* use floating point to avoid strangeness of integer division
16235        with negative dividends on many machines */
16236     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16237
16238     if (second < 0) {
16239         sign = "-";
16240         second = -second;
16241     }
16242
16243     day = second / (60 * 60 * 24);
16244     second = second % (60 * 60 * 24);
16245     hour = second / (60 * 60);
16246     second = second % (60 * 60);
16247     minute = second / 60;
16248     second = second % 60;
16249
16250     if (day > 0)
16251       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16252               sign, day, hour, minute, second);
16253     else if (hour > 0)
16254       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16255     else
16256       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16257
16258     return buf;
16259 }
16260
16261
16262 /*
16263  * This is necessary because some C libraries aren't ANSI C compliant yet.
16264  */
16265 char *
16266 StrStr(string, match)
16267      char *string, *match;
16268 {
16269     int i, length;
16270
16271     length = strlen(match);
16272
16273     for (i = strlen(string) - length; i >= 0; i--, string++)
16274       if (!strncmp(match, string, length))
16275         return string;
16276
16277     return NULL;
16278 }
16279
16280 char *
16281 StrCaseStr(string, match)
16282      char *string, *match;
16283 {
16284     int i, j, length;
16285
16286     length = strlen(match);
16287
16288     for (i = strlen(string) - length; i >= 0; i--, string++) {
16289         for (j = 0; j < length; j++) {
16290             if (ToLower(match[j]) != ToLower(string[j]))
16291               break;
16292         }
16293         if (j == length) return string;
16294     }
16295
16296     return NULL;
16297 }
16298
16299 #ifndef _amigados
16300 int
16301 StrCaseCmp(s1, s2)
16302      char *s1, *s2;
16303 {
16304     char c1, c2;
16305
16306     for (;;) {
16307         c1 = ToLower(*s1++);
16308         c2 = ToLower(*s2++);
16309         if (c1 > c2) return 1;
16310         if (c1 < c2) return -1;
16311         if (c1 == NULLCHAR) return 0;
16312     }
16313 }
16314
16315
16316 int
16317 ToLower(c)
16318      int c;
16319 {
16320     return isupper(c) ? tolower(c) : c;
16321 }
16322
16323
16324 int
16325 ToUpper(c)
16326      int c;
16327 {
16328     return islower(c) ? toupper(c) : c;
16329 }
16330 #endif /* !_amigados    */
16331
16332 char *
16333 StrSave(s)
16334      char *s;
16335 {
16336   char *ret;
16337
16338   if ((ret = (char *) malloc(strlen(s) + 1)))
16339     {
16340       safeStrCpy(ret, s, strlen(s)+1);
16341     }
16342   return ret;
16343 }
16344
16345 char *
16346 StrSavePtr(s, savePtr)
16347      char *s, **savePtr;
16348 {
16349     if (*savePtr) {
16350         free(*savePtr);
16351     }
16352     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16353       safeStrCpy(*savePtr, s, strlen(s)+1);
16354     }
16355     return(*savePtr);
16356 }
16357
16358 char *
16359 PGNDate()
16360 {
16361     time_t clock;
16362     struct tm *tm;
16363     char buf[MSG_SIZ];
16364
16365     clock = time((time_t *)NULL);
16366     tm = localtime(&clock);
16367     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16368             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16369     return StrSave(buf);
16370 }
16371
16372
16373 char *
16374 PositionToFEN(move, overrideCastling)
16375      int move;
16376      char *overrideCastling;
16377 {
16378     int i, j, fromX, fromY, toX, toY;
16379     int whiteToPlay;
16380     char buf[MSG_SIZ];
16381     char *p, *q;
16382     int emptycount;
16383     ChessSquare piece;
16384
16385     whiteToPlay = (gameMode == EditPosition) ?
16386       !blackPlaysFirst : (move % 2 == 0);
16387     p = buf;
16388
16389     /* Piece placement data */
16390     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16391         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16392         emptycount = 0;
16393         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16394             if (boards[move][i][j] == EmptySquare) {
16395                 emptycount++;
16396             } else { ChessSquare piece = boards[move][i][j];
16397                 if (emptycount > 0) {
16398                     if(emptycount<10) /* [HGM] can be >= 10 */
16399                         *p++ = '0' + emptycount;
16400                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16401                     emptycount = 0;
16402                 }
16403                 if(PieceToChar(piece) == '+') {
16404                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16405                     *p++ = '+';
16406                     piece = (ChessSquare)(DEMOTED piece);
16407                 }
16408                 *p++ = PieceToChar(piece);
16409                 if(p[-1] == '~') {
16410                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16411                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16412                     *p++ = '~';
16413                 }
16414             }
16415         }
16416         if (emptycount > 0) {
16417             if(emptycount<10) /* [HGM] can be >= 10 */
16418                 *p++ = '0' + emptycount;
16419             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16420             emptycount = 0;
16421         }
16422         *p++ = '/';
16423     }
16424     *(p - 1) = ' ';
16425
16426     /* [HGM] print Crazyhouse or Shogi holdings */
16427     if( gameInfo.holdingsWidth ) {
16428         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16429         q = p;
16430         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16431             piece = boards[move][i][BOARD_WIDTH-1];
16432             if( piece != EmptySquare )
16433               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16434                   *p++ = PieceToChar(piece);
16435         }
16436         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16437             piece = boards[move][BOARD_HEIGHT-i-1][0];
16438             if( piece != EmptySquare )
16439               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16440                   *p++ = PieceToChar(piece);
16441         }
16442
16443         if( q == p ) *p++ = '-';
16444         *p++ = ']';
16445         *p++ = ' ';
16446     }
16447
16448     /* Active color */
16449     *p++ = whiteToPlay ? 'w' : 'b';
16450     *p++ = ' ';
16451
16452   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16453     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16454   } else {
16455   if(nrCastlingRights) {
16456      q = p;
16457      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16458        /* [HGM] write directly from rights */
16459            if(boards[move][CASTLING][2] != NoRights &&
16460               boards[move][CASTLING][0] != NoRights   )
16461                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16462            if(boards[move][CASTLING][2] != NoRights &&
16463               boards[move][CASTLING][1] != NoRights   )
16464                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16465            if(boards[move][CASTLING][5] != NoRights &&
16466               boards[move][CASTLING][3] != NoRights   )
16467                 *p++ = boards[move][CASTLING][3] + AAA;
16468            if(boards[move][CASTLING][5] != NoRights &&
16469               boards[move][CASTLING][4] != NoRights   )
16470                 *p++ = boards[move][CASTLING][4] + AAA;
16471      } else {
16472
16473         /* [HGM] write true castling rights */
16474         if( nrCastlingRights == 6 ) {
16475             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16476                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16477             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16478                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16479             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16480                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16481             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16482                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16483         }
16484      }
16485      if (q == p) *p++ = '-'; /* No castling rights */
16486      *p++ = ' ';
16487   }
16488
16489   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16490      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16491     /* En passant target square */
16492     if (move > backwardMostMove) {
16493         fromX = moveList[move - 1][0] - AAA;
16494         fromY = moveList[move - 1][1] - ONE;
16495         toX = moveList[move - 1][2] - AAA;
16496         toY = moveList[move - 1][3] - ONE;
16497         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16498             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16499             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16500             fromX == toX) {
16501             /* 2-square pawn move just happened */
16502             *p++ = toX + AAA;
16503             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16504         } else {
16505             *p++ = '-';
16506         }
16507     } else if(move == backwardMostMove) {
16508         // [HGM] perhaps we should always do it like this, and forget the above?
16509         if((signed char)boards[move][EP_STATUS] >= 0) {
16510             *p++ = boards[move][EP_STATUS] + AAA;
16511             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16512         } else {
16513             *p++ = '-';
16514         }
16515     } else {
16516         *p++ = '-';
16517     }
16518     *p++ = ' ';
16519   }
16520   }
16521
16522     /* [HGM] find reversible plies */
16523     {   int i = 0, j=move;
16524
16525         if (appData.debugMode) { int k;
16526             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16527             for(k=backwardMostMove; k<=forwardMostMove; k++)
16528                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16529
16530         }
16531
16532         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16533         if( j == backwardMostMove ) i += initialRulePlies;
16534         sprintf(p, "%d ", i);
16535         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16536     }
16537     /* Fullmove number */
16538     sprintf(p, "%d", (move / 2) + 1);
16539
16540     return StrSave(buf);
16541 }
16542
16543 Boolean
16544 ParseFEN(board, blackPlaysFirst, fen)
16545     Board board;
16546      int *blackPlaysFirst;
16547      char *fen;
16548 {
16549     int i, j;
16550     char *p, c;
16551     int emptycount;
16552     ChessSquare piece;
16553
16554     p = fen;
16555
16556     /* [HGM] by default clear Crazyhouse holdings, if present */
16557     if(gameInfo.holdingsWidth) {
16558        for(i=0; i<BOARD_HEIGHT; i++) {
16559            board[i][0]             = EmptySquare; /* black holdings */
16560            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16561            board[i][1]             = (ChessSquare) 0; /* black counts */
16562            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16563        }
16564     }
16565
16566     /* Piece placement data */
16567     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16568         j = 0;
16569         for (;;) {
16570             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16571                 if (*p == '/') p++;
16572                 emptycount = gameInfo.boardWidth - j;
16573                 while (emptycount--)
16574                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16575                 break;
16576 #if(BOARD_FILES >= 10)
16577             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16578                 p++; emptycount=10;
16579                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16580                 while (emptycount--)
16581                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16582 #endif
16583             } else if (isdigit(*p)) {
16584                 emptycount = *p++ - '0';
16585                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16586                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16587                 while (emptycount--)
16588                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16589             } else if (*p == '+' || isalpha(*p)) {
16590                 if (j >= gameInfo.boardWidth) return FALSE;
16591                 if(*p=='+') {
16592                     piece = CharToPiece(*++p);
16593                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16594                     piece = (ChessSquare) (PROMOTED piece ); p++;
16595                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16596                 } else piece = CharToPiece(*p++);
16597
16598                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16599                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16600                     piece = (ChessSquare) (PROMOTED piece);
16601                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16602                     p++;
16603                 }
16604                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16605             } else {
16606                 return FALSE;
16607             }
16608         }
16609     }
16610     while (*p == '/' || *p == ' ') p++;
16611
16612     /* [HGM] look for Crazyhouse holdings here */
16613     while(*p==' ') p++;
16614     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16615         if(*p == '[') p++;
16616         if(*p == '-' ) p++; /* empty holdings */ else {
16617             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16618             /* if we would allow FEN reading to set board size, we would   */
16619             /* have to add holdings and shift the board read so far here   */
16620             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16621                 p++;
16622                 if((int) piece >= (int) BlackPawn ) {
16623                     i = (int)piece - (int)BlackPawn;
16624                     i = PieceToNumber((ChessSquare)i);
16625                     if( i >= gameInfo.holdingsSize ) return FALSE;
16626                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16627                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16628                 } else {
16629                     i = (int)piece - (int)WhitePawn;
16630                     i = PieceToNumber((ChessSquare)i);
16631                     if( i >= gameInfo.holdingsSize ) return FALSE;
16632                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16633                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16634                 }
16635             }
16636         }
16637         if(*p == ']') p++;
16638     }
16639
16640     while(*p == ' ') p++;
16641
16642     /* Active color */
16643     c = *p++;
16644     if(appData.colorNickNames) {
16645       if( c == appData.colorNickNames[0] ) c = 'w'; else
16646       if( c == appData.colorNickNames[1] ) c = 'b';
16647     }
16648     switch (c) {
16649       case 'w':
16650         *blackPlaysFirst = FALSE;
16651         break;
16652       case 'b':
16653         *blackPlaysFirst = TRUE;
16654         break;
16655       default:
16656         return FALSE;
16657     }
16658
16659     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16660     /* return the extra info in global variiables             */
16661
16662     /* set defaults in case FEN is incomplete */
16663     board[EP_STATUS] = EP_UNKNOWN;
16664     for(i=0; i<nrCastlingRights; i++ ) {
16665         board[CASTLING][i] =
16666             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16667     }   /* assume possible unless obviously impossible */
16668     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16669     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16670     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16671                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16672     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16673     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16674     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16675                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16676     FENrulePlies = 0;
16677
16678     while(*p==' ') p++;
16679     if(nrCastlingRights) {
16680       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16681           /* castling indicator present, so default becomes no castlings */
16682           for(i=0; i<nrCastlingRights; i++ ) {
16683                  board[CASTLING][i] = NoRights;
16684           }
16685       }
16686       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16687              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16688              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16689              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16690         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16691
16692         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16693             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16694             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16695         }
16696         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16697             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16698         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16699                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16700         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16701                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16702         switch(c) {
16703           case'K':
16704               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16705               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16706               board[CASTLING][2] = whiteKingFile;
16707               break;
16708           case'Q':
16709               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16710               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16711               board[CASTLING][2] = whiteKingFile;
16712               break;
16713           case'k':
16714               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16715               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16716               board[CASTLING][5] = blackKingFile;
16717               break;
16718           case'q':
16719               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16720               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16721               board[CASTLING][5] = blackKingFile;
16722           case '-':
16723               break;
16724           default: /* FRC castlings */
16725               if(c >= 'a') { /* black rights */
16726                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16727                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16728                   if(i == BOARD_RGHT) break;
16729                   board[CASTLING][5] = i;
16730                   c -= AAA;
16731                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16732                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16733                   if(c > i)
16734                       board[CASTLING][3] = c;
16735                   else
16736                       board[CASTLING][4] = c;
16737               } else { /* white rights */
16738                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16739                     if(board[0][i] == WhiteKing) break;
16740                   if(i == BOARD_RGHT) break;
16741                   board[CASTLING][2] = i;
16742                   c -= AAA - 'a' + 'A';
16743                   if(board[0][c] >= WhiteKing) break;
16744                   if(c > i)
16745                       board[CASTLING][0] = c;
16746                   else
16747                       board[CASTLING][1] = c;
16748               }
16749         }
16750       }
16751       for(i=0; i<nrCastlingRights; i++)
16752         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16753     if (appData.debugMode) {
16754         fprintf(debugFP, "FEN castling rights:");
16755         for(i=0; i<nrCastlingRights; i++)
16756         fprintf(debugFP, " %d", board[CASTLING][i]);
16757         fprintf(debugFP, "\n");
16758     }
16759
16760       while(*p==' ') p++;
16761     }
16762
16763     /* read e.p. field in games that know e.p. capture */
16764     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16765        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16766       if(*p=='-') {
16767         p++; board[EP_STATUS] = EP_NONE;
16768       } else {
16769          char c = *p++ - AAA;
16770
16771          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16772          if(*p >= '0' && *p <='9') p++;
16773          board[EP_STATUS] = c;
16774       }
16775     }
16776
16777
16778     if(sscanf(p, "%d", &i) == 1) {
16779         FENrulePlies = i; /* 50-move ply counter */
16780         /* (The move number is still ignored)    */
16781     }
16782
16783     return TRUE;
16784 }
16785
16786 void
16787 EditPositionPasteFEN(char *fen)
16788 {
16789   if (fen != NULL) {
16790     Board initial_position;
16791
16792     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16793       DisplayError(_("Bad FEN position in clipboard"), 0);
16794       return ;
16795     } else {
16796       int savedBlackPlaysFirst = blackPlaysFirst;
16797       EditPositionEvent();
16798       blackPlaysFirst = savedBlackPlaysFirst;
16799       CopyBoard(boards[0], initial_position);
16800       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16801       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16802       DisplayBothClocks();
16803       DrawPosition(FALSE, boards[currentMove]);
16804     }
16805   }
16806 }
16807
16808 static char cseq[12] = "\\   ";
16809
16810 Boolean set_cont_sequence(char *new_seq)
16811 {
16812     int len;
16813     Boolean ret;
16814
16815     // handle bad attempts to set the sequence
16816         if (!new_seq)
16817                 return 0; // acceptable error - no debug
16818
16819     len = strlen(new_seq);
16820     ret = (len > 0) && (len < sizeof(cseq));
16821     if (ret)
16822       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16823     else if (appData.debugMode)
16824       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16825     return ret;
16826 }
16827
16828 /*
16829     reformat a source message so words don't cross the width boundary.  internal
16830     newlines are not removed.  returns the wrapped size (no null character unless
16831     included in source message).  If dest is NULL, only calculate the size required
16832     for the dest buffer.  lp argument indicats line position upon entry, and it's
16833     passed back upon exit.
16834 */
16835 int wrap(char *dest, char *src, int count, int width, int *lp)
16836 {
16837     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16838
16839     cseq_len = strlen(cseq);
16840     old_line = line = *lp;
16841     ansi = len = clen = 0;
16842
16843     for (i=0; i < count; i++)
16844     {
16845         if (src[i] == '\033')
16846             ansi = 1;
16847
16848         // if we hit the width, back up
16849         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16850         {
16851             // store i & len in case the word is too long
16852             old_i = i, old_len = len;
16853
16854             // find the end of the last word
16855             while (i && src[i] != ' ' && src[i] != '\n')
16856             {
16857                 i--;
16858                 len--;
16859             }
16860
16861             // word too long?  restore i & len before splitting it
16862             if ((old_i-i+clen) >= width)
16863             {
16864                 i = old_i;
16865                 len = old_len;
16866             }
16867
16868             // extra space?
16869             if (i && src[i-1] == ' ')
16870                 len--;
16871
16872             if (src[i] != ' ' && src[i] != '\n')
16873             {
16874                 i--;
16875                 if (len)
16876                     len--;
16877             }
16878
16879             // now append the newline and continuation sequence
16880             if (dest)
16881                 dest[len] = '\n';
16882             len++;
16883             if (dest)
16884                 strncpy(dest+len, cseq, cseq_len);
16885             len += cseq_len;
16886             line = cseq_len;
16887             clen = cseq_len;
16888             continue;
16889         }
16890
16891         if (dest)
16892             dest[len] = src[i];
16893         len++;
16894         if (!ansi)
16895             line++;
16896         if (src[i] == '\n')
16897             line = 0;
16898         if (src[i] == 'm')
16899             ansi = 0;
16900     }
16901     if (dest && appData.debugMode)
16902     {
16903         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16904             count, width, line, len, *lp);
16905         show_bytes(debugFP, src, count);
16906         fprintf(debugFP, "\ndest: ");
16907         show_bytes(debugFP, dest, len);
16908         fprintf(debugFP, "\n");
16909     }
16910     *lp = dest ? line : old_line;
16911
16912     return len;
16913 }
16914
16915 // [HGM] vari: routines for shelving variations
16916 Boolean modeRestore = FALSE;
16917
16918 void
16919 PushInner(int firstMove, int lastMove)
16920 {
16921         int i, j, nrMoves = lastMove - firstMove;
16922
16923         // push current tail of game on stack
16924         savedResult[storedGames] = gameInfo.result;
16925         savedDetails[storedGames] = gameInfo.resultDetails;
16926         gameInfo.resultDetails = NULL;
16927         savedFirst[storedGames] = firstMove;
16928         savedLast [storedGames] = lastMove;
16929         savedFramePtr[storedGames] = framePtr;
16930         framePtr -= nrMoves; // reserve space for the boards
16931         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16932             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16933             for(j=0; j<MOVE_LEN; j++)
16934                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16935             for(j=0; j<2*MOVE_LEN; j++)
16936                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16937             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16938             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16939             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16940             pvInfoList[firstMove+i-1].depth = 0;
16941             commentList[framePtr+i] = commentList[firstMove+i];
16942             commentList[firstMove+i] = NULL;
16943         }
16944
16945         storedGames++;
16946         forwardMostMove = firstMove; // truncate game so we can start variation
16947 }
16948
16949 void
16950 PushTail(int firstMove, int lastMove)
16951 {
16952         if(appData.icsActive) { // only in local mode
16953                 forwardMostMove = currentMove; // mimic old ICS behavior
16954                 return;
16955         }
16956         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16957
16958         PushInner(firstMove, lastMove);
16959         if(storedGames == 1) GreyRevert(FALSE);
16960         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16961 }
16962
16963 void
16964 PopInner(Boolean annotate)
16965 {
16966         int i, j, nrMoves;
16967         char buf[8000], moveBuf[20];
16968
16969         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16970         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16971         nrMoves = savedLast[storedGames] - currentMove;
16972         if(annotate) {
16973                 int cnt = 10;
16974                 if(!WhiteOnMove(currentMove))
16975                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16976                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16977                 for(i=currentMove; i<forwardMostMove; i++) {
16978                         if(WhiteOnMove(i))
16979                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16980                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16981                         strcat(buf, moveBuf);
16982                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16983                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16984                 }
16985                 strcat(buf, ")");
16986         }
16987         for(i=1; i<=nrMoves; i++) { // copy last variation back
16988             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16989             for(j=0; j<MOVE_LEN; j++)
16990                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16991             for(j=0; j<2*MOVE_LEN; j++)
16992                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16993             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16994             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16995             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16996             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16997             commentList[currentMove+i] = commentList[framePtr+i];
16998             commentList[framePtr+i] = NULL;
16999         }
17000         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17001         framePtr = savedFramePtr[storedGames];
17002         gameInfo.result = savedResult[storedGames];
17003         if(gameInfo.resultDetails != NULL) {
17004             free(gameInfo.resultDetails);
17005       }
17006         gameInfo.resultDetails = savedDetails[storedGames];
17007         forwardMostMove = currentMove + nrMoves;
17008 }
17009
17010 Boolean
17011 PopTail(Boolean annotate)
17012 {
17013         if(appData.icsActive) return FALSE; // only in local mode
17014         if(!storedGames) return FALSE; // sanity
17015         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17016
17017         PopInner(annotate);
17018         if(currentMove < forwardMostMove) ForwardEvent(); else
17019         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17020
17021         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17022         return TRUE;
17023 }
17024
17025 void
17026 CleanupTail()
17027 {       // remove all shelved variations
17028         int i;
17029         for(i=0; i<storedGames; i++) {
17030             if(savedDetails[i])
17031                 free(savedDetails[i]);
17032             savedDetails[i] = NULL;
17033         }
17034         for(i=framePtr; i<MAX_MOVES; i++) {
17035                 if(commentList[i]) free(commentList[i]);
17036                 commentList[i] = NULL;
17037         }
17038         framePtr = MAX_MOVES-1;
17039         storedGames = 0;
17040 }
17041
17042 void
17043 LoadVariation(int index, char *text)
17044 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17045         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17046         int level = 0, move;
17047
17048         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17049         // first find outermost bracketing variation
17050         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17051             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17052                 if(*p == '{') wait = '}'; else
17053                 if(*p == '[') wait = ']'; else
17054                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17055                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17056             }
17057             if(*p == wait) wait = NULLCHAR; // closing ]} found
17058             p++;
17059         }
17060         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17061         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17062         end[1] = NULLCHAR; // clip off comment beyond variation
17063         ToNrEvent(currentMove-1);
17064         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17065         // kludge: use ParsePV() to append variation to game
17066         move = currentMove;
17067         ParsePV(start, TRUE, TRUE);
17068         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17069         ClearPremoveHighlights();
17070         CommentPopDown();
17071         ToNrEvent(currentMove+1);
17072 }
17073