Try to not confuse ICS rating adustments as shouts
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy( char *dst, const char *src, size_t count )
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble(u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags(index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating(str)
647   char *str;
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine(ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions(ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737 "first",
738 "second"
739 };
740
741 void
742 InitEngine(ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine(ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     appData.seedBase = random() + (random()<<15);
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len >= MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len >= MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for draw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantGrand:      /* should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void EscapeExpand(char *p, char *q)
1718 {       // [HGM] initstring: routine to shape up string arguments
1719         while(*p++ = *q++) if(p[-1] == '\\')
1720             switch(*q++) {
1721                 case 'n': p[-1] = '\n'; break;
1722                 case 'r': p[-1] = '\r'; break;
1723                 case 't': p[-1] = '\t'; break;
1724                 case '\\': p[-1] = '\\'; break;
1725                 case 0: *p = 0; return;
1726                 default: p[-1] = q[-1]; break;
1727             }
1728 }
1729
1730 void
1731 show_bytes(fp, buf, count)
1732      FILE *fp;
1733      char *buf;
1734      int count;
1735 {
1736     while (count--) {
1737         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1738             fprintf(fp, "\\%03o", *buf & 0xff);
1739         } else {
1740             putc(*buf, fp);
1741         }
1742         buf++;
1743     }
1744     fflush(fp);
1745 }
1746
1747 /* Returns an errno value */
1748 int
1749 OutputMaybeTelnet(pr, message, count, outError)
1750      ProcRef pr;
1751      char *message;
1752      int count;
1753      int *outError;
1754 {
1755     char buf[8192], *p, *q, *buflim;
1756     int left, newcount, outcount;
1757
1758     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1759         *appData.gateway != NULLCHAR) {
1760         if (appData.debugMode) {
1761             fprintf(debugFP, ">ICS: ");
1762             show_bytes(debugFP, message, count);
1763             fprintf(debugFP, "\n");
1764         }
1765         return OutputToProcess(pr, message, count, outError);
1766     }
1767
1768     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769     p = message;
1770     q = buf;
1771     left = count;
1772     newcount = 0;
1773     while (left) {
1774         if (q >= buflim) {
1775             if (appData.debugMode) {
1776                 fprintf(debugFP, ">ICS: ");
1777                 show_bytes(debugFP, buf, newcount);
1778                 fprintf(debugFP, "\n");
1779             }
1780             outcount = OutputToProcess(pr, buf, newcount, outError);
1781             if (outcount < newcount) return -1; /* to be sure */
1782             q = buf;
1783             newcount = 0;
1784         }
1785         if (*p == '\n') {
1786             *q++ = '\r';
1787             newcount++;
1788         } else if (((unsigned char) *p) == TN_IAC) {
1789             *q++ = (char) TN_IAC;
1790             newcount ++;
1791         }
1792         *q++ = *p++;
1793         newcount++;
1794         left--;
1795     }
1796     if (appData.debugMode) {
1797         fprintf(debugFP, ">ICS: ");
1798         show_bytes(debugFP, buf, newcount);
1799         fprintf(debugFP, "\n");
1800     }
1801     outcount = OutputToProcess(pr, buf, newcount, outError);
1802     if (outcount < newcount) return -1; /* to be sure */
1803     return count;
1804 }
1805
1806 void
1807 read_from_player(isr, closure, message, count, error)
1808      InputSourceRef isr;
1809      VOIDSTAR closure;
1810      char *message;
1811      int count;
1812      int error;
1813 {
1814     int outError, outCount;
1815     static int gotEof = 0;
1816
1817     /* Pass data read from player on to ICS */
1818     if (count > 0) {
1819         gotEof = 0;
1820         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1821         if (outCount < count) {
1822             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1823         }
1824     } else if (count < 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1827     } else if (gotEof++ > 0) {
1828         RemoveInputSource(isr);
1829         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1830     }
1831 }
1832
1833 void
1834 KeepAlive()
1835 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1836     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1837     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1838     SendToICS("date\n");
1839     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1840 }
1841
1842 /* added routine for printf style output to ics */
1843 void ics_printf(char *format, ...)
1844 {
1845     char buffer[MSG_SIZ];
1846     va_list args;
1847
1848     va_start(args, format);
1849     vsnprintf(buffer, sizeof(buffer), format, args);
1850     buffer[sizeof(buffer)-1] = '\0';
1851     SendToICS(buffer);
1852     va_end(args);
1853 }
1854
1855 void
1856 SendToICS(s)
1857      char *s;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NoProc) return;
1862
1863     count = strlen(s);
1864     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1865     if (outCount < count) {
1866         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867     }
1868 }
1869
1870 /* This is used for sending logon scripts to the ICS. Sending
1871    without a delay causes problems when using timestamp on ICC
1872    (at least on my machine). */
1873 void
1874 SendToICSDelayed(s,msdelay)
1875      char *s;
1876      long msdelay;
1877 {
1878     int count, outCount, outError;
1879
1880     if (icsPR == NoProc) return;
1881
1882     count = strlen(s);
1883     if (appData.debugMode) {
1884         fprintf(debugFP, ">ICS: ");
1885         show_bytes(debugFP, s, count);
1886         fprintf(debugFP, "\n");
1887     }
1888     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1889                                       msdelay);
1890     if (outCount < count) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895
1896 /* Remove all highlighting escape sequences in s
1897    Also deletes any suffix starting with '('
1898    */
1899 char *
1900 StripHighlightAndTitle(s)
1901      char *s;
1902 {
1903     static char retbuf[MSG_SIZ];
1904     char *p = retbuf;
1905
1906     while (*s != NULLCHAR) {
1907         while (*s == '\033') {
1908             while (*s != NULLCHAR && !isalpha(*s)) s++;
1909             if (*s != NULLCHAR) s++;
1910         }
1911         while (*s != NULLCHAR && *s != '\033') {
1912             if (*s == '(' || *s == '[') {
1913                 *p = NULLCHAR;
1914                 return retbuf;
1915             }
1916             *p++ = *s++;
1917         }
1918     }
1919     *p = NULLCHAR;
1920     return retbuf;
1921 }
1922
1923 /* Remove all highlighting escape sequences in s */
1924 char *
1925 StripHighlight(s)
1926      char *s;
1927 {
1928     static char retbuf[MSG_SIZ];
1929     char *p = retbuf;
1930
1931     while (*s != NULLCHAR) {
1932         while (*s == '\033') {
1933             while (*s != NULLCHAR && !isalpha(*s)) s++;
1934             if (*s != NULLCHAR) s++;
1935         }
1936         while (*s != NULLCHAR && *s != '\033') {
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 char *variantNames[] = VARIANT_NAMES;
1945 char *
1946 VariantName(v)
1947      VariantClass v;
1948 {
1949     return variantNames[v];
1950 }
1951
1952
1953 /* Identify a variant from the strings the chess servers use or the
1954    PGN Variant tag names we use. */
1955 VariantClass
1956 StringToVariant(e)
1957      char *e;
1958 {
1959     char *p;
1960     int wnum = -1;
1961     VariantClass v = VariantNormal;
1962     int i, found = FALSE;
1963     char buf[MSG_SIZ];
1964     int len;
1965
1966     if (!e) return v;
1967
1968     /* [HGM] skip over optional board-size prefixes */
1969     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1970         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1971         while( *e++ != '_');
1972     }
1973
1974     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1975         v = VariantNormal;
1976         found = TRUE;
1977     } else
1978     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1979       if (StrCaseStr(e, variantNames[i])) {
1980         v = (VariantClass) i;
1981         found = TRUE;
1982         break;
1983       }
1984     }
1985
1986     if (!found) {
1987       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1988           || StrCaseStr(e, "wild/fr")
1989           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1990         v = VariantFischeRandom;
1991       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1992                  (i = 1, p = StrCaseStr(e, "w"))) {
1993         p += i;
1994         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1995         if (isdigit(*p)) {
1996           wnum = atoi(p);
1997         } else {
1998           wnum = -1;
1999         }
2000         switch (wnum) {
2001         case 0: /* FICS only, actually */
2002         case 1:
2003           /* Castling legal even if K starts on d-file */
2004           v = VariantWildCastle;
2005           break;
2006         case 2:
2007         case 3:
2008         case 4:
2009           /* Castling illegal even if K & R happen to start in
2010              normal positions. */
2011           v = VariantNoCastle;
2012           break;
2013         case 5:
2014         case 7:
2015         case 8:
2016         case 10:
2017         case 11:
2018         case 12:
2019         case 13:
2020         case 14:
2021         case 15:
2022         case 18:
2023         case 19:
2024           /* Castling legal iff K & R start in normal positions */
2025           v = VariantNormal;
2026           break;
2027         case 6:
2028         case 20:
2029         case 21:
2030           /* Special wilds for position setup; unclear what to do here */
2031           v = VariantLoadable;
2032           break;
2033         case 9:
2034           /* Bizarre ICC game */
2035           v = VariantTwoKings;
2036           break;
2037         case 16:
2038           v = VariantKriegspiel;
2039           break;
2040         case 17:
2041           v = VariantLosers;
2042           break;
2043         case 22:
2044           v = VariantFischeRandom;
2045           break;
2046         case 23:
2047           v = VariantCrazyhouse;
2048           break;
2049         case 24:
2050           v = VariantBughouse;
2051           break;
2052         case 25:
2053           v = Variant3Check;
2054           break;
2055         case 26:
2056           /* Not quite the same as FICS suicide! */
2057           v = VariantGiveaway;
2058           break;
2059         case 27:
2060           v = VariantAtomic;
2061           break;
2062         case 28:
2063           v = VariantShatranj;
2064           break;
2065
2066         /* Temporary names for future ICC types.  The name *will* change in
2067            the next xboard/WinBoard release after ICC defines it. */
2068         case 29:
2069           v = Variant29;
2070           break;
2071         case 30:
2072           v = Variant30;
2073           break;
2074         case 31:
2075           v = Variant31;
2076           break;
2077         case 32:
2078           v = Variant32;
2079           break;
2080         case 33:
2081           v = Variant33;
2082           break;
2083         case 34:
2084           v = Variant34;
2085           break;
2086         case 35:
2087           v = Variant35;
2088           break;
2089         case 36:
2090           v = Variant36;
2091           break;
2092         case 37:
2093           v = VariantShogi;
2094           break;
2095         case 38:
2096           v = VariantXiangqi;
2097           break;
2098         case 39:
2099           v = VariantCourier;
2100           break;
2101         case 40:
2102           v = VariantGothic;
2103           break;
2104         case 41:
2105           v = VariantCapablanca;
2106           break;
2107         case 42:
2108           v = VariantKnightmate;
2109           break;
2110         case 43:
2111           v = VariantFairy;
2112           break;
2113         case 44:
2114           v = VariantCylinder;
2115           break;
2116         case 45:
2117           v = VariantFalcon;
2118           break;
2119         case 46:
2120           v = VariantCapaRandom;
2121           break;
2122         case 47:
2123           v = VariantBerolina;
2124           break;
2125         case 48:
2126           v = VariantJanus;
2127           break;
2128         case 49:
2129           v = VariantSuper;
2130           break;
2131         case 50:
2132           v = VariantGreat;
2133           break;
2134         case -1:
2135           /* Found "wild" or "w" in the string but no number;
2136              must assume it's normal chess. */
2137           v = VariantNormal;
2138           break;
2139         default:
2140           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2141           if( (len >= MSG_SIZ) && appData.debugMode )
2142             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2143
2144           DisplayError(buf, 0);
2145           v = VariantUnknown;
2146           break;
2147         }
2148       }
2149     }
2150     if (appData.debugMode) {
2151       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2152               e, wnum, VariantName(v));
2153     }
2154     return v;
2155 }
2156
2157 static int leftover_start = 0, leftover_len = 0;
2158 char star_match[STAR_MATCH_N][MSG_SIZ];
2159
2160 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2161    advance *index beyond it, and set leftover_start to the new value of
2162    *index; else return FALSE.  If pattern contains the character '*', it
2163    matches any sequence of characters not containing '\r', '\n', or the
2164    character following the '*' (if any), and the matched sequence(s) are
2165    copied into star_match.
2166    */
2167 int
2168 looking_at(buf, index, pattern)
2169      char *buf;
2170      int *index;
2171      char *pattern;
2172 {
2173     char *bufp = &buf[*index], *patternp = pattern;
2174     int star_count = 0;
2175     char *matchp = star_match[0];
2176
2177     for (;;) {
2178         if (*patternp == NULLCHAR) {
2179             *index = leftover_start = bufp - buf;
2180             *matchp = NULLCHAR;
2181             return TRUE;
2182         }
2183         if (*bufp == NULLCHAR) return FALSE;
2184         if (*patternp == '*') {
2185             if (*bufp == *(patternp + 1)) {
2186                 *matchp = NULLCHAR;
2187                 matchp = star_match[++star_count];
2188                 patternp += 2;
2189                 bufp++;
2190                 continue;
2191             } else if (*bufp == '\n' || *bufp == '\r') {
2192                 patternp++;
2193                 if (*patternp == NULLCHAR)
2194                   continue;
2195                 else
2196                   return FALSE;
2197             } else {
2198                 *matchp++ = *bufp++;
2199                 continue;
2200             }
2201         }
2202         if (*patternp != *bufp) return FALSE;
2203         patternp++;
2204         bufp++;
2205     }
2206 }
2207
2208 void
2209 SendToPlayer(data, length)
2210      char *data;
2211      int length;
2212 {
2213     int error, outCount;
2214     outCount = OutputToProcess(NoProc, data, length, &error);
2215     if (outCount < length) {
2216         DisplayFatalError(_("Error writing to display"), error, 1);
2217     }
2218 }
2219
2220 void
2221 PackHolding(packed, holding)
2222      char packed[];
2223      char *holding;
2224 {
2225     char *p = holding;
2226     char *q = packed;
2227     int runlength = 0;
2228     int curr = 9999;
2229     do {
2230         if (*p == curr) {
2231             runlength++;
2232         } else {
2233             switch (runlength) {
2234               case 0:
2235                 break;
2236               case 1:
2237                 *q++ = curr;
2238                 break;
2239               case 2:
2240                 *q++ = curr;
2241                 *q++ = curr;
2242                 break;
2243               default:
2244                 sprintf(q, "%d", runlength);
2245                 while (*q) q++;
2246                 *q++ = curr;
2247                 break;
2248             }
2249             runlength = 1;
2250             curr = *p;
2251         }
2252     } while (*p++);
2253     *q = NULLCHAR;
2254 }
2255
2256 /* Telnet protocol requests from the front end */
2257 void
2258 TelnetRequest(ddww, option)
2259      unsigned char ddww, option;
2260 {
2261     unsigned char msg[3];
2262     int outCount, outError;
2263
2264     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2265
2266     if (appData.debugMode) {
2267         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2268         switch (ddww) {
2269           case TN_DO:
2270             ddwwStr = "DO";
2271             break;
2272           case TN_DONT:
2273             ddwwStr = "DONT";
2274             break;
2275           case TN_WILL:
2276             ddwwStr = "WILL";
2277             break;
2278           case TN_WONT:
2279             ddwwStr = "WONT";
2280             break;
2281           default:
2282             ddwwStr = buf1;
2283             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2284             break;
2285         }
2286         switch (option) {
2287           case TN_ECHO:
2288             optionStr = "ECHO";
2289             break;
2290           default:
2291             optionStr = buf2;
2292             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2293             break;
2294         }
2295         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2296     }
2297     msg[0] = TN_IAC;
2298     msg[1] = ddww;
2299     msg[2] = option;
2300     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2301     if (outCount < 3) {
2302         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2303     }
2304 }
2305
2306 void
2307 DoEcho()
2308 {
2309     if (!appData.icsActive) return;
2310     TelnetRequest(TN_DO, TN_ECHO);
2311 }
2312
2313 void
2314 DontEcho()
2315 {
2316     if (!appData.icsActive) return;
2317     TelnetRequest(TN_DONT, TN_ECHO);
2318 }
2319
2320 void
2321 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2322 {
2323     /* put the holdings sent to us by the server on the board holdings area */
2324     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2325     char p;
2326     ChessSquare piece;
2327
2328     if(gameInfo.holdingsWidth < 2)  return;
2329     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2330         return; // prevent overwriting by pre-board holdings
2331
2332     if( (int)lowestPiece >= BlackPawn ) {
2333         holdingsColumn = 0;
2334         countsColumn = 1;
2335         holdingsStartRow = BOARD_HEIGHT-1;
2336         direction = -1;
2337     } else {
2338         holdingsColumn = BOARD_WIDTH-1;
2339         countsColumn = BOARD_WIDTH-2;
2340         holdingsStartRow = 0;
2341         direction = 1;
2342     }
2343
2344     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2345         board[i][holdingsColumn] = EmptySquare;
2346         board[i][countsColumn]   = (ChessSquare) 0;
2347     }
2348     while( (p=*holdings++) != NULLCHAR ) {
2349         piece = CharToPiece( ToUpper(p) );
2350         if(piece == EmptySquare) continue;
2351         /*j = (int) piece - (int) WhitePawn;*/
2352         j = PieceToNumber(piece);
2353         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2354         if(j < 0) continue;               /* should not happen */
2355         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2356         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2357         board[holdingsStartRow+j*direction][countsColumn]++;
2358     }
2359 }
2360
2361
2362 void
2363 VariantSwitch(Board board, VariantClass newVariant)
2364 {
2365    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2366    static Board oldBoard;
2367
2368    startedFromPositionFile = FALSE;
2369    if(gameInfo.variant == newVariant) return;
2370
2371    /* [HGM] This routine is called each time an assignment is made to
2372     * gameInfo.variant during a game, to make sure the board sizes
2373     * are set to match the new variant. If that means adding or deleting
2374     * holdings, we shift the playing board accordingly
2375     * This kludge is needed because in ICS observe mode, we get boards
2376     * of an ongoing game without knowing the variant, and learn about the
2377     * latter only later. This can be because of the move list we requested,
2378     * in which case the game history is refilled from the beginning anyway,
2379     * but also when receiving holdings of a crazyhouse game. In the latter
2380     * case we want to add those holdings to the already received position.
2381     */
2382
2383
2384    if (appData.debugMode) {
2385      fprintf(debugFP, "Switch board from %s to %s\n",
2386              VariantName(gameInfo.variant), VariantName(newVariant));
2387      setbuf(debugFP, NULL);
2388    }
2389    shuffleOpenings = 0;       /* [HGM] shuffle */
2390    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2391    switch(newVariant)
2392      {
2393      case VariantShogi:
2394        newWidth = 9;  newHeight = 9;
2395        gameInfo.holdingsSize = 7;
2396      case VariantBughouse:
2397      case VariantCrazyhouse:
2398        newHoldingsWidth = 2; break;
2399      case VariantGreat:
2400        newWidth = 10;
2401      case VariantSuper:
2402        newHoldingsWidth = 2;
2403        gameInfo.holdingsSize = 8;
2404        break;
2405      case VariantGothic:
2406      case VariantCapablanca:
2407      case VariantCapaRandom:
2408        newWidth = 10;
2409      default:
2410        newHoldingsWidth = gameInfo.holdingsSize = 0;
2411      };
2412
2413    if(newWidth  != gameInfo.boardWidth  ||
2414       newHeight != gameInfo.boardHeight ||
2415       newHoldingsWidth != gameInfo.holdingsWidth ) {
2416
2417      /* shift position to new playing area, if needed */
2418      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2419        for(i=0; i<BOARD_HEIGHT; i++)
2420          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2421            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2422              board[i][j];
2423        for(i=0; i<newHeight; i++) {
2424          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2425          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2426        }
2427      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432      }
2433      gameInfo.boardWidth  = newWidth;
2434      gameInfo.boardHeight = newHeight;
2435      gameInfo.holdingsWidth = newHoldingsWidth;
2436      gameInfo.variant = newVariant;
2437      InitDrawingSizes(-2, 0);
2438    } else gameInfo.variant = newVariant;
2439    CopyBoard(oldBoard, board);   // remember correctly formatted board
2440      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2441    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2442 }
2443
2444 static int loggedOn = FALSE;
2445
2446 /*-- Game start info cache: --*/
2447 int gs_gamenum;
2448 char gs_kind[MSG_SIZ];
2449 static char player1Name[128] = "";
2450 static char player2Name[128] = "";
2451 static char cont_seq[] = "\n\\   ";
2452 static int player1Rating = -1;
2453 static int player2Rating = -1;
2454 /*----------------------------*/
2455
2456 ColorClass curColor = ColorNormal;
2457 int suppressKibitz = 0;
2458
2459 // [HGM] seekgraph
2460 Boolean soughtPending = FALSE;
2461 Boolean seekGraphUp;
2462 #define MAX_SEEK_ADS 200
2463 #define SQUARE 0x80
2464 char *seekAdList[MAX_SEEK_ADS];
2465 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2466 float tcList[MAX_SEEK_ADS];
2467 char colorList[MAX_SEEK_ADS];
2468 int nrOfSeekAds = 0;
2469 int minRating = 1010, maxRating = 2800;
2470 int hMargin = 10, vMargin = 20, h, w;
2471 extern int squareSize, lineGap;
2472
2473 void
2474 PlotSeekAd(int i)
2475 {
2476         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2477         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2478         if(r < minRating+100 && r >=0 ) r = minRating+100;
2479         if(r > maxRating) r = maxRating;
2480         if(tc < 1.) tc = 1.;
2481         if(tc > 95.) tc = 95.;
2482         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2483         y = ((double)r - minRating)/(maxRating - minRating)
2484             * (h-vMargin-squareSize/8-1) + vMargin;
2485         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2486         if(strstr(seekAdList[i], " u ")) color = 1;
2487         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2488            !strstr(seekAdList[i], "bullet") &&
2489            !strstr(seekAdList[i], "blitz") &&
2490            !strstr(seekAdList[i], "standard") ) color = 2;
2491         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2492         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2493 }
2494
2495 void
2496 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2497 {
2498         char buf[MSG_SIZ], *ext = "";
2499         VariantClass v = StringToVariant(type);
2500         if(strstr(type, "wild")) {
2501             ext = type + 4; // append wild number
2502             if(v == VariantFischeRandom) type = "chess960"; else
2503             if(v == VariantLoadable) type = "setup"; else
2504             type = VariantName(v);
2505         }
2506         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2507         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2508             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2509             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2510             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2511             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2512             seekNrList[nrOfSeekAds] = nr;
2513             zList[nrOfSeekAds] = 0;
2514             seekAdList[nrOfSeekAds++] = StrSave(buf);
2515             if(plot) PlotSeekAd(nrOfSeekAds-1);
2516         }
2517 }
2518
2519 void
2520 EraseSeekDot(int i)
2521 {
2522     int x = xList[i], y = yList[i], d=squareSize/4, k;
2523     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2524     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2525     // now replot every dot that overlapped
2526     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2527         int xx = xList[k], yy = yList[k];
2528         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2529             DrawSeekDot(xx, yy, colorList[k]);
2530     }
2531 }
2532
2533 void
2534 RemoveSeekAd(int nr)
2535 {
2536         int i;
2537         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2538             EraseSeekDot(i);
2539             if(seekAdList[i]) free(seekAdList[i]);
2540             seekAdList[i] = seekAdList[--nrOfSeekAds];
2541             seekNrList[i] = seekNrList[nrOfSeekAds];
2542             ratingList[i] = ratingList[nrOfSeekAds];
2543             colorList[i]  = colorList[nrOfSeekAds];
2544             tcList[i] = tcList[nrOfSeekAds];
2545             xList[i]  = xList[nrOfSeekAds];
2546             yList[i]  = yList[nrOfSeekAds];
2547             zList[i]  = zList[nrOfSeekAds];
2548             seekAdList[nrOfSeekAds] = NULL;
2549             break;
2550         }
2551 }
2552
2553 Boolean
2554 MatchSoughtLine(char *line)
2555 {
2556     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2557     int nr, base, inc, u=0; char dummy;
2558
2559     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2561        (u=1) &&
2562        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2563         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2564         // match: compact and save the line
2565         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2566         return TRUE;
2567     }
2568     return FALSE;
2569 }
2570
2571 int
2572 DrawSeekGraph()
2573 {
2574     int i;
2575     if(!seekGraphUp) return FALSE;
2576     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2577     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2578
2579     DrawSeekBackground(0, 0, w, h);
2580     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2581     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2582     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2583         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2584         yy = h-1-yy;
2585         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2586         if(i%500 == 0) {
2587             char buf[MSG_SIZ];
2588             snprintf(buf, MSG_SIZ, "%d", i);
2589             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2590         }
2591     }
2592     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2593     for(i=1; i<100; i+=(i<10?1:5)) {
2594         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2595         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2596         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2600         }
2601     }
2602     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2603     return TRUE;
2604 }
2605
2606 int SeekGraphClick(ClickType click, int x, int y, int moving)
2607 {
2608     static int lastDown = 0, displayed = 0, lastSecond;
2609     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2610         if(click == Release || moving) return FALSE;
2611         nrOfSeekAds = 0;
2612         soughtPending = TRUE;
2613         SendToICS(ics_prefix);
2614         SendToICS("sought\n"); // should this be "sought all"?
2615     } else { // issue challenge based on clicked ad
2616         int dist = 10000; int i, closest = 0, second = 0;
2617         for(i=0; i<nrOfSeekAds; i++) {
2618             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2619             if(d < dist) { dist = d; closest = i; }
2620             second += (d - zList[i] < 120); // count in-range ads
2621             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2622         }
2623         if(dist < 120) {
2624             char buf[MSG_SIZ];
2625             second = (second > 1);
2626             if(displayed != closest || second != lastSecond) {
2627                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2628                 lastSecond = second; displayed = closest;
2629             }
2630             if(click == Press) {
2631                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2632                 lastDown = closest;
2633                 return TRUE;
2634             } // on press 'hit', only show info
2635             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2636             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2637             SendToICS(ics_prefix);
2638             SendToICS(buf);
2639             return TRUE; // let incoming board of started game pop down the graph
2640         } else if(click == Release) { // release 'miss' is ignored
2641             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2642             if(moving == 2) { // right up-click
2643                 nrOfSeekAds = 0; // refresh graph
2644                 soughtPending = TRUE;
2645                 SendToICS(ics_prefix);
2646                 SendToICS("sought\n"); // should this be "sought all"?
2647             }
2648             return TRUE;
2649         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2650         // press miss or release hit 'pop down' seek graph
2651         seekGraphUp = FALSE;
2652         DrawPosition(TRUE, NULL);
2653     }
2654     return TRUE;
2655 }
2656
2657 void
2658 read_from_ics(isr, closure, data, count, error)
2659      InputSourceRef isr;
2660      VOIDSTAR closure;
2661      char *data;
2662      int count;
2663      int error;
2664 {
2665 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2666 #define STARTED_NONE 0
2667 #define STARTED_MOVES 1
2668 #define STARTED_BOARD 2
2669 #define STARTED_OBSERVE 3
2670 #define STARTED_HOLDINGS 4
2671 #define STARTED_CHATTER 5
2672 #define STARTED_COMMENT 6
2673 #define STARTED_MOVES_NOHIDE 7
2674
2675     static int started = STARTED_NONE;
2676     static char parse[20000];
2677     static int parse_pos = 0;
2678     static char buf[BUF_SIZE + 1];
2679     static int firstTime = TRUE, intfSet = FALSE;
2680     static ColorClass prevColor = ColorNormal;
2681     static int savingComment = FALSE;
2682     static int cmatch = 0; // continuation sequence match
2683     char *bp;
2684     char str[MSG_SIZ];
2685     int i, oldi;
2686     int buf_len;
2687     int next_out;
2688     int tkind;
2689     int backup;    /* [DM] For zippy color lines */
2690     char *p;
2691     char talker[MSG_SIZ]; // [HGM] chat
2692     int channel;
2693
2694     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2695
2696     if (appData.debugMode) {
2697       if (!error) {
2698         fprintf(debugFP, "<ICS: ");
2699         show_bytes(debugFP, data, count);
2700         fprintf(debugFP, "\n");
2701       }
2702     }
2703
2704     if (appData.debugMode) { int f = forwardMostMove;
2705         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2706                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2707                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2708     }
2709     if (count > 0) {
2710         /* If last read ended with a partial line that we couldn't parse,
2711            prepend it to the new read and try again. */
2712         if (leftover_len > 0) {
2713             for (i=0; i<leftover_len; i++)
2714               buf[i] = buf[leftover_start + i];
2715         }
2716
2717     /* copy new characters into the buffer */
2718     bp = buf + leftover_len;
2719     buf_len=leftover_len;
2720     for (i=0; i<count; i++)
2721     {
2722         // ignore these
2723         if (data[i] == '\r')
2724             continue;
2725
2726         // join lines split by ICS?
2727         if (!appData.noJoin)
2728         {
2729             /*
2730                 Joining just consists of finding matches against the
2731                 continuation sequence, and discarding that sequence
2732                 if found instead of copying it.  So, until a match
2733                 fails, there's nothing to do since it might be the
2734                 complete sequence, and thus, something we don't want
2735                 copied.
2736             */
2737             if (data[i] == cont_seq[cmatch])
2738             {
2739                 cmatch++;
2740                 if (cmatch == strlen(cont_seq))
2741                 {
2742                     cmatch = 0; // complete match.  just reset the counter
2743
2744                     /*
2745                         it's possible for the ICS to not include the space
2746                         at the end of the last word, making our [correct]
2747                         join operation fuse two separate words.  the server
2748                         does this when the space occurs at the width setting.
2749                     */
2750                     if (!buf_len || buf[buf_len-1] != ' ')
2751                     {
2752                         *bp++ = ' ';
2753                         buf_len++;
2754                     }
2755                 }
2756                 continue;
2757             }
2758             else if (cmatch)
2759             {
2760                 /*
2761                     match failed, so we have to copy what matched before
2762                     falling through and copying this character.  In reality,
2763                     this will only ever be just the newline character, but
2764                     it doesn't hurt to be precise.
2765                 */
2766                 strncpy(bp, cont_seq, cmatch);
2767                 bp += cmatch;
2768                 buf_len += cmatch;
2769                 cmatch = 0;
2770             }
2771         }
2772
2773         // copy this char
2774         *bp++ = data[i];
2775         buf_len++;
2776     }
2777
2778         buf[buf_len] = NULLCHAR;
2779 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2780         next_out = 0;
2781         leftover_start = 0;
2782
2783         i = 0;
2784         while (i < buf_len) {
2785             /* Deal with part of the TELNET option negotiation
2786                protocol.  We refuse to do anything beyond the
2787                defaults, except that we allow the WILL ECHO option,
2788                which ICS uses to turn off password echoing when we are
2789                directly connected to it.  We reject this option
2790                if localLineEditing mode is on (always on in xboard)
2791                and we are talking to port 23, which might be a real
2792                telnet server that will try to keep WILL ECHO on permanently.
2793              */
2794             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2795                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2796                 unsigned char option;
2797                 oldi = i;
2798                 switch ((unsigned char) buf[++i]) {
2799                   case TN_WILL:
2800                     if (appData.debugMode)
2801                       fprintf(debugFP, "\n<WILL ");
2802                     switch (option = (unsigned char) buf[++i]) {
2803                       case TN_ECHO:
2804                         if (appData.debugMode)
2805                           fprintf(debugFP, "ECHO ");
2806                         /* Reply only if this is a change, according
2807                            to the protocol rules. */
2808                         if (remoteEchoOption) break;
2809                         if (appData.localLineEditing &&
2810                             atoi(appData.icsPort) == TN_PORT) {
2811                             TelnetRequest(TN_DONT, TN_ECHO);
2812                         } else {
2813                             EchoOff();
2814                             TelnetRequest(TN_DO, TN_ECHO);
2815                             remoteEchoOption = TRUE;
2816                         }
2817                         break;
2818                       default:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "%d ", option);
2821                         /* Whatever this is, we don't want it. */
2822                         TelnetRequest(TN_DONT, option);
2823                         break;
2824                     }
2825                     break;
2826                   case TN_WONT:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<WONT ");
2829                     switch (option = (unsigned char) buf[++i]) {
2830                       case TN_ECHO:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "ECHO ");
2833                         /* Reply only if this is a change, according
2834                            to the protocol rules. */
2835                         if (!remoteEchoOption) break;
2836                         EchoOn();
2837                         TelnetRequest(TN_DONT, TN_ECHO);
2838                         remoteEchoOption = FALSE;
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", (unsigned char) option);
2843                         /* Whatever this is, it must already be turned
2844                            off, because we never agree to turn on
2845                            anything non-default, so according to the
2846                            protocol rules, we don't reply. */
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DO:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DO ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         /* Whatever this is, we refuse to do it. */
2856                         if (appData.debugMode)
2857                           fprintf(debugFP, "%d ", option);
2858                         TelnetRequest(TN_WONT, option);
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DONT:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DONT ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         if (appData.debugMode)
2868                           fprintf(debugFP, "%d ", option);
2869                         /* Whatever this is, we are already not doing
2870                            it, because we never agree to do anything
2871                            non-default, so according to the protocol
2872                            rules, we don't reply. */
2873                         break;
2874                     }
2875                     break;
2876                   case TN_IAC:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<IAC ");
2879                     /* Doubled IAC; pass it through */
2880                     i--;
2881                     break;
2882                   default:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2885                     /* Drop all other telnet commands on the floor */
2886                     break;
2887                 }
2888                 if (oldi > next_out)
2889                   SendToPlayer(&buf[next_out], oldi - next_out);
2890                 if (++i > next_out)
2891                   next_out = i;
2892                 continue;
2893             }
2894
2895             /* OK, this at least will *usually* work */
2896             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2897                 loggedOn = TRUE;
2898             }
2899
2900             if (loggedOn && !intfSet) {
2901                 if (ics_type == ICS_ICC) {
2902                   snprintf(str, MSG_SIZ,
2903                           "/set-quietly interface %s\n/set-quietly style 12\n",
2904                           programVersion);
2905                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2906                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2907                 } else if (ics_type == ICS_CHESSNET) {
2908                   snprintf(str, MSG_SIZ, "/style 12\n");
2909                 } else {
2910                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2911                   strcat(str, programVersion);
2912                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2915 #ifdef WIN32
2916                   strcat(str, "$iset nohighlight 1\n");
2917 #endif
2918                   strcat(str, "$iset lock 1\n$style 12\n");
2919                 }
2920                 SendToICS(str);
2921                 NotifyFrontendLogin();
2922                 intfSet = TRUE;
2923             }
2924
2925             if (started == STARTED_COMMENT) {
2926                 /* Accumulate characters in comment */
2927                 parse[parse_pos++] = buf[i];
2928                 if (buf[i] == '\n') {
2929                     parse[parse_pos] = NULLCHAR;
2930                     if(chattingPartner>=0) {
2931                         char mess[MSG_SIZ];
2932                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2933                         OutputChatMessage(chattingPartner, mess);
2934                         chattingPartner = -1;
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     } else
2937                     if(!suppressKibitz) // [HGM] kibitz
2938                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2939                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2940                         int nrDigit = 0, nrAlph = 0, j;
2941                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2942                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2943                         parse[parse_pos] = NULLCHAR;
2944                         // try to be smart: if it does not look like search info, it should go to
2945                         // ICS interaction window after all, not to engine-output window.
2946                         for(j=0; j<parse_pos; j++) { // count letters and digits
2947                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2948                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2949                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2950                         }
2951                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2952                             int depth=0; float score;
2953                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2954                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2955                                 pvInfoList[forwardMostMove-1].depth = depth;
2956                                 pvInfoList[forwardMostMove-1].score = 100*score;
2957                             }
2958                             OutputKibitz(suppressKibitz, parse);
2959                         } else {
2960                             char tmp[MSG_SIZ];
2961                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2962                             SendToPlayer(tmp, strlen(tmp));
2963                         }
2964                         next_out = i+1; // [HGM] suppress printing in ICS window
2965                     }
2966                     started = STARTED_NONE;
2967                 } else {
2968                     /* Don't match patterns against characters in comment */
2969                     i++;
2970                     continue;
2971                 }
2972             }
2973             if (started == STARTED_CHATTER) {
2974                 if (buf[i] != '\n') {
2975                     /* Don't match patterns against characters in chatter */
2976                     i++;
2977                     continue;
2978                 }
2979                 started = STARTED_NONE;
2980                 if(suppressKibitz) next_out = i+1;
2981             }
2982
2983             /* Kludge to deal with rcmd protocol */
2984             if (firstTime && looking_at(buf, &i, "\001*")) {
2985                 DisplayFatalError(&buf[1], 0, 1);
2986                 continue;
2987             } else {
2988                 firstTime = FALSE;
2989             }
2990
2991             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2992                 ics_type = ICS_ICC;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2999                 ics_type = ICS_FICS;
3000                 ics_prefix = "$";
3001                 if (appData.debugMode)
3002                   fprintf(debugFP, "ics_type %d\n", ics_type);
3003                 continue;
3004             }
3005             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3006                 ics_type = ICS_CHESSNET;
3007                 ics_prefix = "/";
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, "ics_type %d\n", ics_type);
3010                 continue;
3011             }
3012
3013             if (!loggedOn &&
3014                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3015                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3016                  looking_at(buf, &i, "will be \"*\""))) {
3017               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3018               continue;
3019             }
3020
3021             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3022               char buf[MSG_SIZ];
3023               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3024               DisplayIcsInteractionTitle(buf);
3025               have_set_title = TRUE;
3026             }
3027
3028             /* skip finger notes */
3029             if (started == STARTED_NONE &&
3030                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3031                  (buf[i] == '1' && buf[i+1] == '0')) &&
3032                 buf[i+2] == ':' && buf[i+3] == ' ') {
3033               started = STARTED_CHATTER;
3034               i += 3;
3035               continue;
3036             }
3037
3038             oldi = i;
3039             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3040             if(appData.seekGraph) {
3041                 if(soughtPending && MatchSoughtLine(buf+i)) {
3042                     i = strstr(buf+i, "rated") - buf;
3043                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044                     next_out = leftover_start = i;
3045                     started = STARTED_CHATTER;
3046                     suppressKibitz = TRUE;
3047                     continue;
3048                 }
3049                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3050                         && looking_at(buf, &i, "* ads displayed")) {
3051                     soughtPending = FALSE;
3052                     seekGraphUp = TRUE;
3053                     DrawSeekGraph();
3054                     continue;
3055                 }
3056                 if(appData.autoRefresh) {
3057                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3058                         int s = (ics_type == ICS_ICC); // ICC format differs
3059                         if(seekGraphUp)
3060                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3061                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3064                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                         next_out = i; // suppress
3066                         continue;
3067                     }
3068                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3069                         char *p = star_match[0];
3070                         while(*p) {
3071                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3072                             while(*p && *p++ != ' '); // next
3073                         }
3074                         looking_at(buf, &i, "*% "); // eat prompt
3075                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                         next_out = i;
3077                         continue;
3078                     }
3079                 }
3080             }
3081
3082             /* skip formula vars */
3083             if (started == STARTED_NONE &&
3084                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3091             if (appData.autoKibitz && started == STARTED_NONE &&
3092                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3093                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3094                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3095                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3096                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3097                         suppressKibitz = TRUE;
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3101                                 && (gameMode == IcsPlayingWhite)) ||
3102                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3103                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3104                             started = STARTED_CHATTER; // own kibitz we simply discard
3105                         else {
3106                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3107                             parse_pos = 0; parse[0] = NULLCHAR;
3108                             savingComment = TRUE;
3109                             suppressKibitz = gameMode != IcsObserving ? 2 :
3110                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3111                         }
3112                         continue;
3113                 } else
3114                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3115                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3116                          && atoi(star_match[0])) {
3117                     // suppress the acknowledgements of our own autoKibitz
3118                     char *p;
3119                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3121                     SendToPlayer(star_match[0], strlen(star_match[0]));
3122                     if(looking_at(buf, &i, "*% ")) // eat prompt
3123                         suppressKibitz = FALSE;
3124                     next_out = i;
3125                     continue;
3126                 }
3127             } // [HGM] kibitz: end of patch
3128
3129             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3130
3131             // [HGM] chat: intercept tells by users for which we have an open chat window
3132             channel = -1;
3133             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3134                                            looking_at(buf, &i, "* whispers:") ||
3135                                            looking_at(buf, &i, "* kibitzes:") ||
3136                                            looking_at(buf, &i, "* shouts:") ||
3137                                            looking_at(buf, &i, "* c-shouts:") ||
3138                                            looking_at(buf, &i, "--> * ") ||
3139                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3140                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3141                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3142                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3143                 int p;
3144                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3145                 chattingPartner = -1;
3146
3147                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3148                 for(p=0; p<MAX_CHAT; p++) {
3149                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3150                     talker[0] = '['; strcat(talker, "] ");
3151                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3152                     chattingPartner = p; break;
3153                     }
3154                 } else
3155                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3156                 for(p=0; p<MAX_CHAT; p++) {
3157                     if(!strcmp("kibitzes", chatPartner[p])) {
3158                         talker[0] = '['; strcat(talker, "] ");
3159                         chattingPartner = p; break;
3160                     }
3161                 } else
3162                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3163                 for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("whispers", chatPartner[p])) {
3165                         talker[0] = '['; strcat(talker, "] ");
3166                         chattingPartner = p; break;
3167                     }
3168                 } else
3169                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3170                   if(buf[i-8] == '-' && buf[i-3] == 't')
3171                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3172                     if(!strcmp("c-shouts", chatPartner[p])) {
3173                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3174                         chattingPartner = p; break;
3175                     }
3176                   }
3177                   if(chattingPartner < 0)
3178                   for(p=0; p<MAX_CHAT; p++) {
3179                     if(!strcmp("shouts", chatPartner[p])) {
3180                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3181                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3182                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3183                         chattingPartner = p; break;
3184                     }
3185                   }
3186                 }
3187                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3188                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3189                     talker[0] = 0; Colorize(ColorTell, FALSE);
3190                     chattingPartner = p; break;
3191                 }
3192                 if(chattingPartner<0) i = oldi; else {
3193                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3194                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3195                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3196                     started = STARTED_COMMENT;
3197                     parse_pos = 0; parse[0] = NULLCHAR;
3198                     savingComment = 3 + chattingPartner; // counts as TRUE
3199                     suppressKibitz = TRUE;
3200                     continue;
3201                 }
3202             } // [HGM] chat: end of patch
3203
3204           backup = i;
3205             if (appData.zippyTalk || appData.zippyPlay) {
3206                 /* [DM] Backup address for color zippy lines */
3207 #if ZIPPY
3208                if (loggedOn == TRUE)
3209                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3210                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3211 #endif
3212             } // [DM] 'else { ' deleted
3213                 if (
3214                     /* Regular tells and says */
3215                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3216                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3217                     looking_at(buf, &i, "* says: ") ||
3218                     /* Don't color "message" or "messages" output */
3219                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3220                     looking_at(buf, &i, "*. * at *:*: ") ||
3221                     looking_at(buf, &i, "--* (*:*): ") ||
3222                     /* Message notifications (same color as tells) */
3223                     looking_at(buf, &i, "* has left a message ") ||
3224                     looking_at(buf, &i, "* just sent you a message:\n") ||
3225                     /* Whispers and kibitzes */
3226                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3227                     looking_at(buf, &i, "* kibitzes: ") ||
3228                     /* Channel tells */
3229                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3230
3231                   if (tkind == 1 && strchr(star_match[0], ':')) {
3232                       /* Avoid "tells you:" spoofs in channels */
3233                      tkind = 3;
3234                   }
3235                   if (star_match[0][0] == NULLCHAR ||
3236                       strchr(star_match[0], ' ') ||
3237                       (tkind == 3 && strchr(star_match[1], ' '))) {
3238                     /* Reject bogus matches */
3239                     i = oldi;
3240                   } else {
3241                     if (appData.colorize) {
3242                       if (oldi > next_out) {
3243                         SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = oldi;
3245                       }
3246                       switch (tkind) {
3247                       case 1:
3248                         Colorize(ColorTell, FALSE);
3249                         curColor = ColorTell;
3250                         break;
3251                       case 2:
3252                         Colorize(ColorKibitz, FALSE);
3253                         curColor = ColorKibitz;
3254                         break;
3255                       case 3:
3256                         p = strrchr(star_match[1], '(');
3257                         if (p == NULL) {
3258                           p = star_match[1];
3259                         } else {
3260                           p++;
3261                         }
3262                         if (atoi(p) == 1) {
3263                           Colorize(ColorChannel1, FALSE);
3264                           curColor = ColorChannel1;
3265                         } else {
3266                           Colorize(ColorChannel, FALSE);
3267                           curColor = ColorChannel;
3268                         }
3269                         break;
3270                       case 5:
3271                         curColor = ColorNormal;
3272                         break;
3273                       }
3274                     }
3275                     if (started == STARTED_NONE && appData.autoComment &&
3276                         (gameMode == IcsObserving ||
3277                          gameMode == IcsPlayingWhite ||
3278                          gameMode == IcsPlayingBlack)) {
3279                       parse_pos = i - oldi;
3280                       memcpy(parse, &buf[oldi], parse_pos);
3281                       parse[parse_pos] = NULLCHAR;
3282                       started = STARTED_COMMENT;
3283                       savingComment = TRUE;
3284                     } else {
3285                       started = STARTED_CHATTER;
3286                       savingComment = FALSE;
3287                     }
3288                     loggedOn = TRUE;
3289                     continue;
3290                   }
3291                 }
3292
3293                 if (looking_at(buf, &i, "* s-shouts: ") ||
3294                     looking_at(buf, &i, "* c-shouts: ")) {
3295                     if (appData.colorize) {
3296                         if (oldi > next_out) {
3297                             SendToPlayer(&buf[next_out], oldi - next_out);
3298                             next_out = oldi;
3299                         }
3300                         Colorize(ColorSShout, FALSE);
3301                         curColor = ColorSShout;
3302                     }
3303                     loggedOn = TRUE;
3304                     started = STARTED_CHATTER;
3305                     continue;
3306                 }
3307
3308                 if (looking_at(buf, &i, "--->")) {
3309                     loggedOn = TRUE;
3310                     continue;
3311                 }
3312
3313                 if (looking_at(buf, &i, "* shouts: ") ||
3314                     looking_at(buf, &i, "--> ")) {
3315                     if (appData.colorize) {
3316                         if (oldi > next_out) {
3317                             SendToPlayer(&buf[next_out], oldi - next_out);
3318                             next_out = oldi;
3319                         }
3320                         Colorize(ColorShout, FALSE);
3321                         curColor = ColorShout;
3322                     }
3323                     loggedOn = TRUE;
3324                     started = STARTED_CHATTER;
3325                     continue;
3326                 }
3327
3328                 if (looking_at( buf, &i, "Challenge:")) {
3329                     if (appData.colorize) {
3330                         if (oldi > next_out) {
3331                             SendToPlayer(&buf[next_out], oldi - next_out);
3332                             next_out = oldi;
3333                         }
3334                         Colorize(ColorChallenge, FALSE);
3335                         curColor = ColorChallenge;
3336                     }
3337                     loggedOn = TRUE;
3338                     continue;
3339                 }
3340
3341                 if (looking_at(buf, &i, "* offers you") ||
3342                     looking_at(buf, &i, "* offers to be") ||
3343                     looking_at(buf, &i, "* would like to") ||
3344                     looking_at(buf, &i, "* requests to") ||
3345                     looking_at(buf, &i, "Your opponent offers") ||
3346                     looking_at(buf, &i, "Your opponent requests")) {
3347
3348                     if (appData.colorize) {
3349                         if (oldi > next_out) {
3350                             SendToPlayer(&buf[next_out], oldi - next_out);
3351                             next_out = oldi;
3352                         }
3353                         Colorize(ColorRequest, FALSE);
3354                         curColor = ColorRequest;
3355                     }
3356                     continue;
3357                 }
3358
3359                 if (looking_at(buf, &i, "* (*) seeking")) {
3360                     if (appData.colorize) {
3361                         if (oldi > next_out) {
3362                             SendToPlayer(&buf[next_out], oldi - next_out);
3363                             next_out = oldi;
3364                         }
3365                         Colorize(ColorSeek, FALSE);
3366                         curColor = ColorSeek;
3367                     }
3368                     continue;
3369             }
3370
3371           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3372
3373             if (looking_at(buf, &i, "\\   ")) {
3374                 if (prevColor != ColorNormal) {
3375                     if (oldi > next_out) {
3376                         SendToPlayer(&buf[next_out], oldi - next_out);
3377                         next_out = oldi;
3378                     }
3379                     Colorize(prevColor, TRUE);
3380                     curColor = prevColor;
3381                 }
3382                 if (savingComment) {
3383                     parse_pos = i - oldi;
3384                     memcpy(parse, &buf[oldi], parse_pos);
3385                     parse[parse_pos] = NULLCHAR;
3386                     started = STARTED_COMMENT;
3387                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3388                         chattingPartner = savingComment - 3; // kludge to remember the box
3389                 } else {
3390                     started = STARTED_CHATTER;
3391                 }
3392                 continue;
3393             }
3394
3395             if (looking_at(buf, &i, "Black Strength :") ||
3396                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3397                 looking_at(buf, &i, "<10>") ||
3398                 looking_at(buf, &i, "#@#")) {
3399                 /* Wrong board style */
3400                 loggedOn = TRUE;
3401                 SendToICS(ics_prefix);
3402                 SendToICS("set style 12\n");
3403                 SendToICS(ics_prefix);
3404                 SendToICS("refresh\n");
3405                 continue;
3406             }
3407
3408             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3409                 ICSInitScript();
3410                 have_sent_ICS_logon = 1;
3411                 continue;
3412             }
3413
3414             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3415                 (looking_at(buf, &i, "\n<12> ") ||
3416                  looking_at(buf, &i, "<12> "))) {
3417                 loggedOn = TRUE;
3418                 if (oldi > next_out) {
3419                     SendToPlayer(&buf[next_out], oldi - next_out);
3420                 }
3421                 next_out = i;
3422                 started = STARTED_BOARD;
3423                 parse_pos = 0;
3424                 continue;
3425             }
3426
3427             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3428                 looking_at(buf, &i, "<b1> ")) {
3429                 if (oldi > next_out) {
3430                     SendToPlayer(&buf[next_out], oldi - next_out);
3431                 }
3432                 next_out = i;
3433                 started = STARTED_HOLDINGS;
3434                 parse_pos = 0;
3435                 continue;
3436             }
3437
3438             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3439                 loggedOn = TRUE;
3440                 /* Header for a move list -- first line */
3441
3442                 switch (ics_getting_history) {
3443                   case H_FALSE:
3444                     switch (gameMode) {
3445                       case IcsIdle:
3446                       case BeginningOfGame:
3447                         /* User typed "moves" or "oldmoves" while we
3448                            were idle.  Pretend we asked for these
3449                            moves and soak them up so user can step
3450                            through them and/or save them.
3451                            */
3452                         Reset(FALSE, TRUE);
3453                         gameMode = IcsObserving;
3454                         ModeHighlight();
3455                         ics_gamenum = -1;
3456                         ics_getting_history = H_GOT_UNREQ_HEADER;
3457                         break;
3458                       case EditGame: /*?*/
3459                       case EditPosition: /*?*/
3460                         /* Should above feature work in these modes too? */
3461                         /* For now it doesn't */
3462                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3463                         break;
3464                       default:
3465                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3466                         break;
3467                     }
3468                     break;
3469                   case H_REQUESTED:
3470                     /* Is this the right one? */
3471                     if (gameInfo.white && gameInfo.black &&
3472                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3473                         strcmp(gameInfo.black, star_match[2]) == 0) {
3474                         /* All is well */
3475                         ics_getting_history = H_GOT_REQ_HEADER;
3476                     }
3477                     break;
3478                   case H_GOT_REQ_HEADER:
3479                   case H_GOT_UNREQ_HEADER:
3480                   case H_GOT_UNWANTED_HEADER:
3481                   case H_GETTING_MOVES:
3482                     /* Should not happen */
3483                     DisplayError(_("Error gathering move list: two headers"), 0);
3484                     ics_getting_history = H_FALSE;
3485                     break;
3486                 }
3487
3488                 /* Save player ratings into gameInfo if needed */
3489                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3490                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3491                     (gameInfo.whiteRating == -1 ||
3492                      gameInfo.blackRating == -1)) {
3493
3494                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3495                     gameInfo.blackRating = string_to_rating(star_match[3]);
3496                     if (appData.debugMode)
3497                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3498                               gameInfo.whiteRating, gameInfo.blackRating);
3499                 }
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i,
3504               "* * match, initial time: * minute*, increment: * second")) {
3505                 /* Header for a move list -- second line */
3506                 /* Initial board will follow if this is a wild game */
3507                 if (gameInfo.event != NULL) free(gameInfo.event);
3508                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3509                 gameInfo.event = StrSave(str);
3510                 /* [HGM] we switched variant. Translate boards if needed. */
3511                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3512                 continue;
3513             }
3514
3515             if (looking_at(buf, &i, "Move  ")) {
3516                 /* Beginning of a move list */
3517                 switch (ics_getting_history) {
3518                   case H_FALSE:
3519                     /* Normally should not happen */
3520                     /* Maybe user hit reset while we were parsing */
3521                     break;
3522                   case H_REQUESTED:
3523                     /* Happens if we are ignoring a move list that is not
3524                      * the one we just requested.  Common if the user
3525                      * tries to observe two games without turning off
3526                      * getMoveList */
3527                     break;
3528                   case H_GETTING_MOVES:
3529                     /* Should not happen */
3530                     DisplayError(_("Error gathering move list: nested"), 0);
3531                     ics_getting_history = H_FALSE;
3532                     break;
3533                   case H_GOT_REQ_HEADER:
3534                     ics_getting_history = H_GETTING_MOVES;
3535                     started = STARTED_MOVES;
3536                     parse_pos = 0;
3537                     if (oldi > next_out) {
3538                         SendToPlayer(&buf[next_out], oldi - next_out);
3539                     }
3540                     break;
3541                   case H_GOT_UNREQ_HEADER:
3542                     ics_getting_history = H_GETTING_MOVES;
3543                     started = STARTED_MOVES_NOHIDE;
3544                     parse_pos = 0;
3545                     break;
3546                   case H_GOT_UNWANTED_HEADER:
3547                     ics_getting_history = H_FALSE;
3548                     break;
3549                 }
3550                 continue;
3551             }
3552
3553             if (looking_at(buf, &i, "% ") ||
3554                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3555                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3556                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3557                     soughtPending = FALSE;
3558                     seekGraphUp = TRUE;
3559                     DrawSeekGraph();
3560                 }
3561                 if(suppressKibitz) next_out = i;
3562                 savingComment = FALSE;
3563                 suppressKibitz = 0;
3564                 switch (started) {
3565                   case STARTED_MOVES:
3566                   case STARTED_MOVES_NOHIDE:
3567                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3568                     parse[parse_pos + i - oldi] = NULLCHAR;
3569                     ParseGameHistory(parse);
3570 #if ZIPPY
3571                     if (appData.zippyPlay && first.initDone) {
3572                         FeedMovesToProgram(&first, forwardMostMove);
3573                         if (gameMode == IcsPlayingWhite) {
3574                             if (WhiteOnMove(forwardMostMove)) {
3575                                 if (first.sendTime) {
3576                                   if (first.useColors) {
3577                                     SendToProgram("black\n", &first);
3578                                   }
3579                                   SendTimeRemaining(&first, TRUE);
3580                                 }
3581                                 if (first.useColors) {
3582                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3583                                 }
3584                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3585                                 first.maybeThinking = TRUE;
3586                             } else {
3587                                 if (first.usePlayother) {
3588                                   if (first.sendTime) {
3589                                     SendTimeRemaining(&first, TRUE);
3590                                   }
3591                                   SendToProgram("playother\n", &first);
3592                                   firstMove = FALSE;
3593                                 } else {
3594                                   firstMove = TRUE;
3595                                 }
3596                             }
3597                         } else if (gameMode == IcsPlayingBlack) {
3598                             if (!WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("white\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, FALSE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("black\n", &first);
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, FALSE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         }
3622                     }
3623 #endif
3624                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3625                         /* Moves came from oldmoves or moves command
3626                            while we weren't doing anything else.
3627                            */
3628                         currentMove = forwardMostMove;
3629                         ClearHighlights();/*!!could figure this out*/
3630                         flipView = appData.flipView;
3631                         DrawPosition(TRUE, boards[currentMove]);
3632                         DisplayBothClocks();
3633                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3634                                 gameInfo.white, gameInfo.black);
3635                         DisplayTitle(str);
3636                         gameMode = IcsIdle;
3637                     } else {
3638                         /* Moves were history of an active game */
3639                         if (gameInfo.resultDetails != NULL) {
3640                             free(gameInfo.resultDetails);
3641                             gameInfo.resultDetails = NULL;
3642                         }
3643                     }
3644                     HistorySet(parseList, backwardMostMove,
3645                                forwardMostMove, currentMove-1);
3646                     DisplayMove(currentMove - 1);
3647                     if (started == STARTED_MOVES) next_out = i;
3648                     started = STARTED_NONE;
3649                     ics_getting_history = H_FALSE;
3650                     break;
3651
3652                   case STARTED_OBSERVE:
3653                     started = STARTED_NONE;
3654                     SendToICS(ics_prefix);
3655                     SendToICS("refresh\n");
3656                     break;
3657
3658                   default:
3659                     break;
3660                 }
3661                 if(bookHit) { // [HGM] book: simulate book reply
3662                     static char bookMove[MSG_SIZ]; // a bit generous?
3663
3664                     programStats.nodes = programStats.depth = programStats.time =
3665                     programStats.score = programStats.got_only_move = 0;
3666                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3667
3668                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3669                     strcat(bookMove, bookHit);
3670                     HandleMachineMove(bookMove, &first);
3671                 }
3672                 continue;
3673             }
3674
3675             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3676                  started == STARTED_HOLDINGS ||
3677                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3678                 /* Accumulate characters in move list or board */
3679                 parse[parse_pos++] = buf[i];
3680             }
3681
3682             /* Start of game messages.  Mostly we detect start of game
3683                when the first board image arrives.  On some versions
3684                of the ICS, though, we need to do a "refresh" after starting
3685                to observe in order to get the current board right away. */
3686             if (looking_at(buf, &i, "Adding game * to observation list")) {
3687                 started = STARTED_OBSERVE;
3688                 continue;
3689             }
3690
3691             /* Handle auto-observe */
3692             if (appData.autoObserve &&
3693                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3694                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3695                 char *player;
3696                 /* Choose the player that was highlighted, if any. */
3697                 if (star_match[0][0] == '\033' ||
3698                     star_match[1][0] != '\033') {
3699                     player = star_match[0];
3700                 } else {
3701                     player = star_match[2];
3702                 }
3703                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3704                         ics_prefix, StripHighlightAndTitle(player));
3705                 SendToICS(str);
3706
3707                 /* Save ratings from notify string */
3708                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3709                 player1Rating = string_to_rating(star_match[1]);
3710                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3711                 player2Rating = string_to_rating(star_match[3]);
3712
3713                 if (appData.debugMode)
3714                   fprintf(debugFP,
3715                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3716                           player1Name, player1Rating,
3717                           player2Name, player2Rating);
3718
3719                 continue;
3720             }
3721
3722             /* Deal with automatic examine mode after a game,
3723                and with IcsObserving -> IcsExamining transition */
3724             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3725                 looking_at(buf, &i, "has made you an examiner of game *")) {
3726
3727                 int gamenum = atoi(star_match[0]);
3728                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3729                     gamenum == ics_gamenum) {
3730                     /* We were already playing or observing this game;
3731                        no need to refetch history */
3732                     gameMode = IcsExamining;
3733                     if (pausing) {
3734                         pauseExamForwardMostMove = forwardMostMove;
3735                     } else if (currentMove < forwardMostMove) {
3736                         ForwardInner(forwardMostMove);
3737                     }
3738                 } else {
3739                     /* I don't think this case really can happen */
3740                     SendToICS(ics_prefix);
3741                     SendToICS("refresh\n");
3742                 }
3743                 continue;
3744             }
3745
3746             /* Error messages */
3747 //          if (ics_user_moved) {
3748             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3749                 if (looking_at(buf, &i, "Illegal move") ||
3750                     looking_at(buf, &i, "Not a legal move") ||
3751                     looking_at(buf, &i, "Your king is in check") ||
3752                     looking_at(buf, &i, "It isn't your turn") ||
3753                     looking_at(buf, &i, "It is not your move")) {
3754                     /* Illegal move */
3755                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3756                         currentMove = forwardMostMove-1;
3757                         DisplayMove(currentMove - 1); /* before DMError */
3758                         DrawPosition(FALSE, boards[currentMove]);
3759                         SwitchClocks(forwardMostMove-1); // [HGM] race
3760                         DisplayBothClocks();
3761                     }
3762                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3763                     ics_user_moved = 0;
3764                     continue;
3765                 }
3766             }
3767
3768             if (looking_at(buf, &i, "still have time") ||
3769                 looking_at(buf, &i, "not out of time") ||
3770                 looking_at(buf, &i, "either player is out of time") ||
3771                 looking_at(buf, &i, "has timeseal; checking")) {
3772                 /* We must have called his flag a little too soon */
3773                 whiteFlag = blackFlag = FALSE;
3774                 continue;
3775             }
3776
3777             if (looking_at(buf, &i, "added * seconds to") ||
3778                 looking_at(buf, &i, "seconds were added to")) {
3779                 /* Update the clocks */
3780                 SendToICS(ics_prefix);
3781                 SendToICS("refresh\n");
3782                 continue;
3783             }
3784
3785             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3786                 ics_clock_paused = TRUE;
3787                 StopClocks();
3788                 continue;
3789             }
3790
3791             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3792                 ics_clock_paused = FALSE;
3793                 StartClocks();
3794                 continue;
3795             }
3796
3797             /* Grab player ratings from the Creating: message.
3798                Note we have to check for the special case when
3799                the ICS inserts things like [white] or [black]. */
3800             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3801                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3802                 /* star_matches:
3803                    0    player 1 name (not necessarily white)
3804                    1    player 1 rating
3805                    2    empty, white, or black (IGNORED)
3806                    3    player 2 name (not necessarily black)
3807                    4    player 2 rating
3808
3809                    The names/ratings are sorted out when the game
3810                    actually starts (below).
3811                 */
3812                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3813                 player1Rating = string_to_rating(star_match[1]);
3814                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3815                 player2Rating = string_to_rating(star_match[4]);
3816
3817                 if (appData.debugMode)
3818                   fprintf(debugFP,
3819                           "Ratings from 'Creating:' %s %d, %s %d\n",
3820                           player1Name, player1Rating,
3821                           player2Name, player2Rating);
3822
3823                 continue;
3824             }
3825
3826             /* Improved generic start/end-of-game messages */
3827             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3828                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3829                 /* If tkind == 0: */
3830                 /* star_match[0] is the game number */
3831                 /*           [1] is the white player's name */
3832                 /*           [2] is the black player's name */
3833                 /* For end-of-game: */
3834                 /*           [3] is the reason for the game end */
3835                 /*           [4] is a PGN end game-token, preceded by " " */
3836                 /* For start-of-game: */
3837                 /*           [3] begins with "Creating" or "Continuing" */
3838                 /*           [4] is " *" or empty (don't care). */
3839                 int gamenum = atoi(star_match[0]);
3840                 char *whitename, *blackname, *why, *endtoken;
3841                 ChessMove endtype = EndOfFile;
3842
3843                 if (tkind == 0) {
3844                   whitename = star_match[1];
3845                   blackname = star_match[2];
3846                   why = star_match[3];
3847                   endtoken = star_match[4];
3848                 } else {
3849                   whitename = star_match[1];
3850                   blackname = star_match[3];
3851                   why = star_match[5];
3852                   endtoken = star_match[6];
3853                 }
3854
3855                 /* Game start messages */
3856                 if (strncmp(why, "Creating ", 9) == 0 ||
3857                     strncmp(why, "Continuing ", 11) == 0) {
3858                     gs_gamenum = gamenum;
3859                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3860                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3861                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3862 #if ZIPPY
3863                     if (appData.zippyPlay) {
3864                         ZippyGameStart(whitename, blackname);
3865                     }
3866 #endif /*ZIPPY*/
3867                     partnerBoardValid = FALSE; // [HGM] bughouse
3868                     continue;
3869                 }
3870
3871                 /* Game end messages */
3872                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3873                     ics_gamenum != gamenum) {
3874                     continue;
3875                 }
3876                 while (endtoken[0] == ' ') endtoken++;
3877                 switch (endtoken[0]) {
3878                   case '*':
3879                   default:
3880                     endtype = GameUnfinished;
3881                     break;
3882                   case '0':
3883                     endtype = BlackWins;
3884                     break;
3885                   case '1':
3886                     if (endtoken[1] == '/')
3887                       endtype = GameIsDrawn;
3888                     else
3889                       endtype = WhiteWins;
3890                     break;
3891                 }
3892                 GameEnds(endtype, why, GE_ICS);
3893 #if ZIPPY
3894                 if (appData.zippyPlay && first.initDone) {
3895                     ZippyGameEnd(endtype, why);
3896                     if (first.pr == NoProc) {
3897                       /* Start the next process early so that we'll
3898                          be ready for the next challenge */
3899                       StartChessProgram(&first);
3900                     }
3901                     /* Send "new" early, in case this command takes
3902                        a long time to finish, so that we'll be ready
3903                        for the next challenge. */
3904                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3905                     Reset(TRUE, TRUE);
3906                 }
3907 #endif /*ZIPPY*/
3908                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3909                 continue;
3910             }
3911
3912             if (looking_at(buf, &i, "Removing game * from observation") ||
3913                 looking_at(buf, &i, "no longer observing game *") ||
3914                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3915                 if (gameMode == IcsObserving &&
3916                     atoi(star_match[0]) == ics_gamenum)
3917                   {
3918                       /* icsEngineAnalyze */
3919                       if (appData.icsEngineAnalyze) {
3920                             ExitAnalyzeMode();
3921                             ModeHighlight();
3922                       }
3923                       StopClocks();
3924                       gameMode = IcsIdle;
3925                       ics_gamenum = -1;
3926                       ics_user_moved = FALSE;
3927                   }
3928                 continue;
3929             }
3930
3931             if (looking_at(buf, &i, "no longer examining game *")) {
3932                 if (gameMode == IcsExamining &&
3933                     atoi(star_match[0]) == ics_gamenum)
3934                   {
3935                       gameMode = IcsIdle;
3936                       ics_gamenum = -1;
3937                       ics_user_moved = FALSE;
3938                   }
3939                 continue;
3940             }
3941
3942             /* Advance leftover_start past any newlines we find,
3943                so only partial lines can get reparsed */
3944             if (looking_at(buf, &i, "\n")) {
3945                 prevColor = curColor;
3946                 if (curColor != ColorNormal) {
3947                     if (oldi > next_out) {
3948                         SendToPlayer(&buf[next_out], oldi - next_out);
3949                         next_out = oldi;
3950                     }
3951                     Colorize(ColorNormal, FALSE);
3952                     curColor = ColorNormal;
3953                 }
3954                 if (started == STARTED_BOARD) {
3955                     started = STARTED_NONE;
3956                     parse[parse_pos] = NULLCHAR;
3957                     ParseBoard12(parse);
3958                     ics_user_moved = 0;
3959
3960                     /* Send premove here */
3961                     if (appData.premove) {
3962                       char str[MSG_SIZ];
3963                       if (currentMove == 0 &&
3964                           gameMode == IcsPlayingWhite &&
3965                           appData.premoveWhite) {
3966                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3967                         if (appData.debugMode)
3968                           fprintf(debugFP, "Sending premove:\n");
3969                         SendToICS(str);
3970                       } else if (currentMove == 1 &&
3971                                  gameMode == IcsPlayingBlack &&
3972                                  appData.premoveBlack) {
3973                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                         SendToICS(str);
3977                       } else if (gotPremove) {
3978                         gotPremove = 0;
3979                         ClearPremoveHighlights();
3980                         if (appData.debugMode)
3981                           fprintf(debugFP, "Sending premove:\n");
3982                           UserMoveEvent(premoveFromX, premoveFromY,
3983                                         premoveToX, premoveToY,
3984                                         premovePromoChar);
3985                       }
3986                     }
3987
3988                     /* Usually suppress following prompt */
3989                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3990                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3991                         if (looking_at(buf, &i, "*% ")) {
3992                             savingComment = FALSE;
3993                             suppressKibitz = 0;
3994                         }
3995                     }
3996                     next_out = i;
3997                 } else if (started == STARTED_HOLDINGS) {
3998                     int gamenum;
3999                     char new_piece[MSG_SIZ];
4000                     started = STARTED_NONE;
4001                     parse[parse_pos] = NULLCHAR;
4002                     if (appData.debugMode)
4003                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4004                                                         parse, currentMove);
4005                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4006                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4007                         if (gameInfo.variant == VariantNormal) {
4008                           /* [HGM] We seem to switch variant during a game!
4009                            * Presumably no holdings were displayed, so we have
4010                            * to move the position two files to the right to
4011                            * create room for them!
4012                            */
4013                           VariantClass newVariant;
4014                           switch(gameInfo.boardWidth) { // base guess on board width
4015                                 case 9:  newVariant = VariantShogi; break;
4016                                 case 10: newVariant = VariantGreat; break;
4017                                 default: newVariant = VariantCrazyhouse; break;
4018                           }
4019                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4020                           /* Get a move list just to see the header, which
4021                              will tell us whether this is really bug or zh */
4022                           if (ics_getting_history == H_FALSE) {
4023                             ics_getting_history = H_REQUESTED;
4024                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4025                             SendToICS(str);
4026                           }
4027                         }
4028                         new_piece[0] = NULLCHAR;
4029                         sscanf(parse, "game %d white [%s black [%s <- %s",
4030                                &gamenum, white_holding, black_holding,
4031                                new_piece);
4032                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4033                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4034                         /* [HGM] copy holdings to board holdings area */
4035                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4036                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4037                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4038 #if ZIPPY
4039                         if (appData.zippyPlay && first.initDone) {
4040                             ZippyHoldings(white_holding, black_holding,
4041                                           new_piece);
4042                         }
4043 #endif /*ZIPPY*/
4044                         if (tinyLayout || smallLayout) {
4045                             char wh[16], bh[16];
4046                             PackHolding(wh, white_holding);
4047                             PackHolding(bh, black_holding);
4048                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4049                                     gameInfo.white, gameInfo.black);
4050                         } else {
4051                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4052                                     gameInfo.white, white_holding,
4053                                     gameInfo.black, black_holding);
4054                         }
4055                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4056                         DrawPosition(FALSE, boards[currentMove]);
4057                         DisplayTitle(str);
4058                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4059                         sscanf(parse, "game %d white [%s black [%s <- %s",
4060                                &gamenum, white_holding, black_holding,
4061                                new_piece);
4062                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4063                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4064                         /* [HGM] copy holdings to partner-board holdings area */
4065                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4066                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4067                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4068                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4069                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4070                       }
4071                     }
4072                     /* Suppress following prompt */
4073                     if (looking_at(buf, &i, "*% ")) {
4074                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4075                         savingComment = FALSE;
4076                         suppressKibitz = 0;
4077                     }
4078                     next_out = i;
4079                 }
4080                 continue;
4081             }
4082
4083             i++;                /* skip unparsed character and loop back */
4084         }
4085
4086         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4087 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4088 //          SendToPlayer(&buf[next_out], i - next_out);
4089             started != STARTED_HOLDINGS && leftover_start > next_out) {
4090             SendToPlayer(&buf[next_out], leftover_start - next_out);
4091             next_out = i;
4092         }
4093
4094         leftover_len = buf_len - leftover_start;
4095         /* if buffer ends with something we couldn't parse,
4096            reparse it after appending the next read */
4097
4098     } else if (count == 0) {
4099         RemoveInputSource(isr);
4100         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4101     } else {
4102         DisplayFatalError(_("Error reading from ICS"), error, 1);
4103     }
4104 }
4105
4106
4107 /* Board style 12 looks like this:
4108
4109    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4110
4111  * The "<12> " is stripped before it gets to this routine.  The two
4112  * trailing 0's (flip state and clock ticking) are later addition, and
4113  * some chess servers may not have them, or may have only the first.
4114  * Additional trailing fields may be added in the future.
4115  */
4116
4117 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4118
4119 #define RELATION_OBSERVING_PLAYED    0
4120 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4121 #define RELATION_PLAYING_MYMOVE      1
4122 #define RELATION_PLAYING_NOTMYMOVE  -1
4123 #define RELATION_EXAMINING           2
4124 #define RELATION_ISOLATED_BOARD     -3
4125 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4126
4127 void
4128 ParseBoard12(string)
4129      char *string;
4130 {
4131     GameMode newGameMode;
4132     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4133     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4134     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4135     char to_play, board_chars[200];
4136     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4137     char black[32], white[32];
4138     Board board;
4139     int prevMove = currentMove;
4140     int ticking = 2;
4141     ChessMove moveType;
4142     int fromX, fromY, toX, toY;
4143     char promoChar;
4144     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4145     char *bookHit = NULL; // [HGM] book
4146     Boolean weird = FALSE, reqFlag = FALSE;
4147
4148     fromX = fromY = toX = toY = -1;
4149
4150     newGame = FALSE;
4151
4152     if (appData.debugMode)
4153       fprintf(debugFP, _("Parsing board: %s\n"), string);
4154
4155     move_str[0] = NULLCHAR;
4156     elapsed_time[0] = NULLCHAR;
4157     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4158         int  i = 0, j;
4159         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4160             if(string[i] == ' ') { ranks++; files = 0; }
4161             else files++;
4162             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4163             i++;
4164         }
4165         for(j = 0; j <i; j++) board_chars[j] = string[j];
4166         board_chars[i] = '\0';
4167         string += i + 1;
4168     }
4169     n = sscanf(string, PATTERN, &to_play, &double_push,
4170                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4171                &gamenum, white, black, &relation, &basetime, &increment,
4172                &white_stren, &black_stren, &white_time, &black_time,
4173                &moveNum, str, elapsed_time, move_str, &ics_flip,
4174                &ticking);
4175
4176     if (n < 21) {
4177         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4178         DisplayError(str, 0);
4179         return;
4180     }
4181
4182     /* Convert the move number to internal form */
4183     moveNum = (moveNum - 1) * 2;
4184     if (to_play == 'B') moveNum++;
4185     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4186       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4187                         0, 1);
4188       return;
4189     }
4190
4191     switch (relation) {
4192       case RELATION_OBSERVING_PLAYED:
4193       case RELATION_OBSERVING_STATIC:
4194         if (gamenum == -1) {
4195             /* Old ICC buglet */
4196             relation = RELATION_OBSERVING_STATIC;
4197         }
4198         newGameMode = IcsObserving;
4199         break;
4200       case RELATION_PLAYING_MYMOVE:
4201       case RELATION_PLAYING_NOTMYMOVE:
4202         newGameMode =
4203           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4204             IcsPlayingWhite : IcsPlayingBlack;
4205         break;
4206       case RELATION_EXAMINING:
4207         newGameMode = IcsExamining;
4208         break;
4209       case RELATION_ISOLATED_BOARD:
4210       default:
4211         /* Just display this board.  If user was doing something else,
4212            we will forget about it until the next board comes. */
4213         newGameMode = IcsIdle;
4214         break;
4215       case RELATION_STARTING_POSITION:
4216         newGameMode = gameMode;
4217         break;
4218     }
4219
4220     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4221          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4222       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4223       char *toSqr;
4224       for (k = 0; k < ranks; k++) {
4225         for (j = 0; j < files; j++)
4226           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4227         if(gameInfo.holdingsWidth > 1) {
4228              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4229              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4230         }
4231       }
4232       CopyBoard(partnerBoard, board);
4233       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4234         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4235         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4236       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4237       if(toSqr = strchr(str, '-')) {
4238         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4239         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4240       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4241       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4242       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4243       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4244       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4245       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4246                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4247       DisplayMessage(partnerStatus, "");
4248         partnerBoardValid = TRUE;
4249       return;
4250     }
4251
4252     /* Modify behavior for initial board display on move listing
4253        of wild games.
4254        */
4255     switch (ics_getting_history) {
4256       case H_FALSE:
4257       case H_REQUESTED:
4258         break;
4259       case H_GOT_REQ_HEADER:
4260       case H_GOT_UNREQ_HEADER:
4261         /* This is the initial position of the current game */
4262         gamenum = ics_gamenum;
4263         moveNum = 0;            /* old ICS bug workaround */
4264         if (to_play == 'B') {
4265           startedFromSetupPosition = TRUE;
4266           blackPlaysFirst = TRUE;
4267           moveNum = 1;
4268           if (forwardMostMove == 0) forwardMostMove = 1;
4269           if (backwardMostMove == 0) backwardMostMove = 1;
4270           if (currentMove == 0) currentMove = 1;
4271         }
4272         newGameMode = gameMode;
4273         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4274         break;
4275       case H_GOT_UNWANTED_HEADER:
4276         /* This is an initial board that we don't want */
4277         return;
4278       case H_GETTING_MOVES:
4279         /* Should not happen */
4280         DisplayError(_("Error gathering move list: extra board"), 0);
4281         ics_getting_history = H_FALSE;
4282         return;
4283     }
4284
4285    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4286                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4287      /* [HGM] We seem to have switched variant unexpectedly
4288       * Try to guess new variant from board size
4289       */
4290           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4291           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4292           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4293           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4294           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4295           if(!weird) newVariant = VariantNormal;
4296           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4297           /* Get a move list just to see the header, which
4298              will tell us whether this is really bug or zh */
4299           if (ics_getting_history == H_FALSE) {
4300             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4301             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4302             SendToICS(str);
4303           }
4304     }
4305
4306     /* Take action if this is the first board of a new game, or of a
4307        different game than is currently being displayed.  */
4308     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4309         relation == RELATION_ISOLATED_BOARD) {
4310
4311         /* Forget the old game and get the history (if any) of the new one */
4312         if (gameMode != BeginningOfGame) {
4313           Reset(TRUE, TRUE);
4314         }
4315         newGame = TRUE;
4316         if (appData.autoRaiseBoard) BoardToTop();
4317         prevMove = -3;
4318         if (gamenum == -1) {
4319             newGameMode = IcsIdle;
4320         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4321                    appData.getMoveList && !reqFlag) {
4322             /* Need to get game history */
4323             ics_getting_history = H_REQUESTED;
4324             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4325             SendToICS(str);
4326         }
4327
4328         /* Initially flip the board to have black on the bottom if playing
4329            black or if the ICS flip flag is set, but let the user change
4330            it with the Flip View button. */
4331         flipView = appData.autoFlipView ?
4332           (newGameMode == IcsPlayingBlack) || ics_flip :
4333           appData.flipView;
4334
4335         /* Done with values from previous mode; copy in new ones */
4336         gameMode = newGameMode;
4337         ModeHighlight();
4338         ics_gamenum = gamenum;
4339         if (gamenum == gs_gamenum) {
4340             int klen = strlen(gs_kind);
4341             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4342             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4343             gameInfo.event = StrSave(str);
4344         } else {
4345             gameInfo.event = StrSave("ICS game");
4346         }
4347         gameInfo.site = StrSave(appData.icsHost);
4348         gameInfo.date = PGNDate();
4349         gameInfo.round = StrSave("-");
4350         gameInfo.white = StrSave(white);
4351         gameInfo.black = StrSave(black);
4352         timeControl = basetime * 60 * 1000;
4353         timeControl_2 = 0;
4354         timeIncrement = increment * 1000;
4355         movesPerSession = 0;
4356         gameInfo.timeControl = TimeControlTagValue();
4357         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4358   if (appData.debugMode) {
4359     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4360     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4361     setbuf(debugFP, NULL);
4362   }
4363
4364         gameInfo.outOfBook = NULL;
4365
4366         /* Do we have the ratings? */
4367         if (strcmp(player1Name, white) == 0 &&
4368             strcmp(player2Name, black) == 0) {
4369             if (appData.debugMode)
4370               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4371                       player1Rating, player2Rating);
4372             gameInfo.whiteRating = player1Rating;
4373             gameInfo.blackRating = player2Rating;
4374         } else if (strcmp(player2Name, white) == 0 &&
4375                    strcmp(player1Name, black) == 0) {
4376             if (appData.debugMode)
4377               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4378                       player2Rating, player1Rating);
4379             gameInfo.whiteRating = player2Rating;
4380             gameInfo.blackRating = player1Rating;
4381         }
4382         player1Name[0] = player2Name[0] = NULLCHAR;
4383
4384         /* Silence shouts if requested */
4385         if (appData.quietPlay &&
4386             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4387             SendToICS(ics_prefix);
4388             SendToICS("set shout 0\n");
4389         }
4390     }
4391
4392     /* Deal with midgame name changes */
4393     if (!newGame) {
4394         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4395             if (gameInfo.white) free(gameInfo.white);
4396             gameInfo.white = StrSave(white);
4397         }
4398         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4399             if (gameInfo.black) free(gameInfo.black);
4400             gameInfo.black = StrSave(black);
4401         }
4402     }
4403
4404     /* Throw away game result if anything actually changes in examine mode */
4405     if (gameMode == IcsExamining && !newGame) {
4406         gameInfo.result = GameUnfinished;
4407         if (gameInfo.resultDetails != NULL) {
4408             free(gameInfo.resultDetails);
4409             gameInfo.resultDetails = NULL;
4410         }
4411     }
4412
4413     /* In pausing && IcsExamining mode, we ignore boards coming
4414        in if they are in a different variation than we are. */
4415     if (pauseExamInvalid) return;
4416     if (pausing && gameMode == IcsExamining) {
4417         if (moveNum <= pauseExamForwardMostMove) {
4418             pauseExamInvalid = TRUE;
4419             forwardMostMove = pauseExamForwardMostMove;
4420             return;
4421         }
4422     }
4423
4424   if (appData.debugMode) {
4425     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4426   }
4427     /* Parse the board */
4428     for (k = 0; k < ranks; k++) {
4429       for (j = 0; j < files; j++)
4430         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4431       if(gameInfo.holdingsWidth > 1) {
4432            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4433            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4434       }
4435     }
4436     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4437       board[5][BOARD_RGHT+1] = WhiteAngel;
4438       board[6][BOARD_RGHT+1] = WhiteMarshall;
4439       board[1][0] = BlackMarshall;
4440       board[2][0] = BlackAngel;
4441       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4442     }
4443     CopyBoard(boards[moveNum], board);
4444     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4445     if (moveNum == 0) {
4446         startedFromSetupPosition =
4447           !CompareBoards(board, initialPosition);
4448         if(startedFromSetupPosition)
4449             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4450     }
4451
4452     /* [HGM] Set castling rights. Take the outermost Rooks,
4453        to make it also work for FRC opening positions. Note that board12
4454        is really defective for later FRC positions, as it has no way to
4455        indicate which Rook can castle if they are on the same side of King.
4456        For the initial position we grant rights to the outermost Rooks,
4457        and remember thos rights, and we then copy them on positions
4458        later in an FRC game. This means WB might not recognize castlings with
4459        Rooks that have moved back to their original position as illegal,
4460        but in ICS mode that is not its job anyway.
4461     */
4462     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4463     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4464
4465         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4466             if(board[0][i] == WhiteRook) j = i;
4467         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4468         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4469             if(board[0][i] == WhiteRook) j = i;
4470         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4471         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4472             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4473         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4474         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4475             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4476         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4477
4478         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4479         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4480         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4481             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4482         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4483             if(board[BOARD_HEIGHT-1][k] == bKing)
4484                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4485         if(gameInfo.variant == VariantTwoKings) {
4486             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4487             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4488             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4489         }
4490     } else { int r;
4491         r = boards[moveNum][CASTLING][0] = initialRights[0];
4492         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4493         r = boards[moveNum][CASTLING][1] = initialRights[1];
4494         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4495         r = boards[moveNum][CASTLING][3] = initialRights[3];
4496         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4497         r = boards[moveNum][CASTLING][4] = initialRights[4];
4498         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4499         /* wildcastle kludge: always assume King has rights */
4500         r = boards[moveNum][CASTLING][2] = initialRights[2];
4501         r = boards[moveNum][CASTLING][5] = initialRights[5];
4502     }
4503     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4504     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4505
4506
4507     if (ics_getting_history == H_GOT_REQ_HEADER ||
4508         ics_getting_history == H_GOT_UNREQ_HEADER) {
4509         /* This was an initial position from a move list, not
4510            the current position */
4511         return;
4512     }
4513
4514     /* Update currentMove and known move number limits */
4515     newMove = newGame || moveNum > forwardMostMove;
4516
4517     if (newGame) {
4518         forwardMostMove = backwardMostMove = currentMove = moveNum;
4519         if (gameMode == IcsExamining && moveNum == 0) {
4520           /* Workaround for ICS limitation: we are not told the wild
4521              type when starting to examine a game.  But if we ask for
4522              the move list, the move list header will tell us */
4523             ics_getting_history = H_REQUESTED;
4524             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4525             SendToICS(str);
4526         }
4527     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4528                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4529 #if ZIPPY
4530         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4531         /* [HGM] applied this also to an engine that is silently watching        */
4532         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4533             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4534             gameInfo.variant == currentlyInitializedVariant) {
4535           takeback = forwardMostMove - moveNum;
4536           for (i = 0; i < takeback; i++) {
4537             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4538             SendToProgram("undo\n", &first);
4539           }
4540         }
4541 #endif
4542
4543         forwardMostMove = moveNum;
4544         if (!pausing || currentMove > forwardMostMove)
4545           currentMove = forwardMostMove;
4546     } else {
4547         /* New part of history that is not contiguous with old part */
4548         if (pausing && gameMode == IcsExamining) {
4549             pauseExamInvalid = TRUE;
4550             forwardMostMove = pauseExamForwardMostMove;
4551             return;
4552         }
4553         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4554 #if ZIPPY
4555             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4556                 // [HGM] when we will receive the move list we now request, it will be
4557                 // fed to the engine from the first move on. So if the engine is not
4558                 // in the initial position now, bring it there.
4559                 InitChessProgram(&first, 0);
4560             }
4561 #endif
4562             ics_getting_history = H_REQUESTED;
4563             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4564             SendToICS(str);
4565         }
4566         forwardMostMove = backwardMostMove = currentMove = moveNum;
4567     }
4568
4569     /* Update the clocks */
4570     if (strchr(elapsed_time, '.')) {
4571       /* Time is in ms */
4572       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4573       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4574     } else {
4575       /* Time is in seconds */
4576       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4577       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4578     }
4579
4580
4581 #if ZIPPY
4582     if (appData.zippyPlay && newGame &&
4583         gameMode != IcsObserving && gameMode != IcsIdle &&
4584         gameMode != IcsExamining)
4585       ZippyFirstBoard(moveNum, basetime, increment);
4586 #endif
4587
4588     /* Put the move on the move list, first converting
4589        to canonical algebraic form. */
4590     if (moveNum > 0) {
4591   if (appData.debugMode) {
4592     if (appData.debugMode) { int f = forwardMostMove;
4593         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4594                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4595                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4596     }
4597     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4598     fprintf(debugFP, "moveNum = %d\n", moveNum);
4599     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4600     setbuf(debugFP, NULL);
4601   }
4602         if (moveNum <= backwardMostMove) {
4603             /* We don't know what the board looked like before
4604                this move.  Punt. */
4605           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4606             strcat(parseList[moveNum - 1], " ");
4607             strcat(parseList[moveNum - 1], elapsed_time);
4608             moveList[moveNum - 1][0] = NULLCHAR;
4609         } else if (strcmp(move_str, "none") == 0) {
4610             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4611             /* Again, we don't know what the board looked like;
4612                this is really the start of the game. */
4613             parseList[moveNum - 1][0] = NULLCHAR;
4614             moveList[moveNum - 1][0] = NULLCHAR;
4615             backwardMostMove = moveNum;
4616             startedFromSetupPosition = TRUE;
4617             fromX = fromY = toX = toY = -1;
4618         } else {
4619           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4620           //                 So we parse the long-algebraic move string in stead of the SAN move
4621           int valid; char buf[MSG_SIZ], *prom;
4622
4623           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4624                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4625           // str looks something like "Q/a1-a2"; kill the slash
4626           if(str[1] == '/')
4627             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4628           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4629           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4630                 strcat(buf, prom); // long move lacks promo specification!
4631           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4632                 if(appData.debugMode)
4633                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4634                 safeStrCpy(move_str, buf, MSG_SIZ);
4635           }
4636           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4637                                 &fromX, &fromY, &toX, &toY, &promoChar)
4638                || ParseOneMove(buf, moveNum - 1, &moveType,
4639                                 &fromX, &fromY, &toX, &toY, &promoChar);
4640           // end of long SAN patch
4641           if (valid) {
4642             (void) CoordsToAlgebraic(boards[moveNum - 1],
4643                                      PosFlags(moveNum - 1),
4644                                      fromY, fromX, toY, toX, promoChar,
4645                                      parseList[moveNum-1]);
4646             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4647               case MT_NONE:
4648               case MT_STALEMATE:
4649               default:
4650                 break;
4651               case MT_CHECK:
4652                 if(gameInfo.variant != VariantShogi)
4653                     strcat(parseList[moveNum - 1], "+");
4654                 break;
4655               case MT_CHECKMATE:
4656               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4657                 strcat(parseList[moveNum - 1], "#");
4658                 break;
4659             }
4660             strcat(parseList[moveNum - 1], " ");
4661             strcat(parseList[moveNum - 1], elapsed_time);
4662             /* currentMoveString is set as a side-effect of ParseOneMove */
4663             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4664             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4665             strcat(moveList[moveNum - 1], "\n");
4666
4667             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4668                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4669               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4670                 ChessSquare old, new = boards[moveNum][k][j];
4671                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4672                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4673                   if(old == new) continue;
4674                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4675                   else if(new == WhiteWazir || new == BlackWazir) {
4676                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4677                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4678                       else boards[moveNum][k][j] = old; // preserve type of Gold
4679                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4680                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4681               }
4682           } else {
4683             /* Move from ICS was illegal!?  Punt. */
4684             if (appData.debugMode) {
4685               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4686               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4687             }
4688             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4689             strcat(parseList[moveNum - 1], " ");
4690             strcat(parseList[moveNum - 1], elapsed_time);
4691             moveList[moveNum - 1][0] = NULLCHAR;
4692             fromX = fromY = toX = toY = -1;
4693           }
4694         }
4695   if (appData.debugMode) {
4696     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4697     setbuf(debugFP, NULL);
4698   }
4699
4700 #if ZIPPY
4701         /* Send move to chess program (BEFORE animating it). */
4702         if (appData.zippyPlay && !newGame && newMove &&
4703            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4704
4705             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4706                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4707                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4708                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4709                             move_str);
4710                     DisplayError(str, 0);
4711                 } else {
4712                     if (first.sendTime) {
4713                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4714                     }
4715                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4716                     if (firstMove && !bookHit) {
4717                         firstMove = FALSE;
4718                         if (first.useColors) {
4719                           SendToProgram(gameMode == IcsPlayingWhite ?
4720                                         "white\ngo\n" :
4721                                         "black\ngo\n", &first);
4722                         } else {
4723                           SendToProgram("go\n", &first);
4724                         }
4725                         first.maybeThinking = TRUE;
4726                     }
4727                 }
4728             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4729               if (moveList[moveNum - 1][0] == NULLCHAR) {
4730                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4731                 DisplayError(str, 0);
4732               } else {
4733                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4734                 SendMoveToProgram(moveNum - 1, &first);
4735               }
4736             }
4737         }
4738 #endif
4739     }
4740
4741     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4742         /* If move comes from a remote source, animate it.  If it
4743            isn't remote, it will have already been animated. */
4744         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4745             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4746         }
4747         if (!pausing && appData.highlightLastMove) {
4748             SetHighlights(fromX, fromY, toX, toY);
4749         }
4750     }
4751
4752     /* Start the clocks */
4753     whiteFlag = blackFlag = FALSE;
4754     appData.clockMode = !(basetime == 0 && increment == 0);
4755     if (ticking == 0) {
4756       ics_clock_paused = TRUE;
4757       StopClocks();
4758     } else if (ticking == 1) {
4759       ics_clock_paused = FALSE;
4760     }
4761     if (gameMode == IcsIdle ||
4762         relation == RELATION_OBSERVING_STATIC ||
4763         relation == RELATION_EXAMINING ||
4764         ics_clock_paused)
4765       DisplayBothClocks();
4766     else
4767       StartClocks();
4768
4769     /* Display opponents and material strengths */
4770     if (gameInfo.variant != VariantBughouse &&
4771         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4772         if (tinyLayout || smallLayout) {
4773             if(gameInfo.variant == VariantNormal)
4774               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4775                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4776                     basetime, increment);
4777             else
4778               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4779                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4780                     basetime, increment, (int) gameInfo.variant);
4781         } else {
4782             if(gameInfo.variant == VariantNormal)
4783               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4784                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4785                     basetime, increment);
4786             else
4787               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4788                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4789                     basetime, increment, VariantName(gameInfo.variant));
4790         }
4791         DisplayTitle(str);
4792   if (appData.debugMode) {
4793     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4794   }
4795     }
4796
4797
4798     /* Display the board */
4799     if (!pausing && !appData.noGUI) {
4800
4801       if (appData.premove)
4802           if (!gotPremove ||
4803              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4804              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4805               ClearPremoveHighlights();
4806
4807       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4808         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4809       DrawPosition(j, boards[currentMove]);
4810
4811       DisplayMove(moveNum - 1);
4812       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4813             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4814               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4815         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4816       }
4817     }
4818
4819     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4820 #if ZIPPY
4821     if(bookHit) { // [HGM] book: simulate book reply
4822         static char bookMove[MSG_SIZ]; // a bit generous?
4823
4824         programStats.nodes = programStats.depth = programStats.time =
4825         programStats.score = programStats.got_only_move = 0;
4826         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4827
4828         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4829         strcat(bookMove, bookHit);
4830         HandleMachineMove(bookMove, &first);
4831     }
4832 #endif
4833 }
4834
4835 void
4836 GetMoveListEvent()
4837 {
4838     char buf[MSG_SIZ];
4839     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4840         ics_getting_history = H_REQUESTED;
4841         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4842         SendToICS(buf);
4843     }
4844 }
4845
4846 void
4847 AnalysisPeriodicEvent(force)
4848      int force;
4849 {
4850     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4851          && !force) || !appData.periodicUpdates)
4852       return;
4853
4854     /* Send . command to Crafty to collect stats */
4855     SendToProgram(".\n", &first);
4856
4857     /* Don't send another until we get a response (this makes
4858        us stop sending to old Crafty's which don't understand
4859        the "." command (sending illegal cmds resets node count & time,
4860        which looks bad)) */
4861     programStats.ok_to_send = 0;
4862 }
4863
4864 void ics_update_width(new_width)
4865         int new_width;
4866 {
4867         ics_printf("set width %d\n", new_width);
4868 }
4869
4870 void
4871 SendMoveToProgram(moveNum, cps)
4872      int moveNum;
4873      ChessProgramState *cps;
4874 {
4875     char buf[MSG_SIZ];
4876
4877     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4878         // null move in variant where engine does not understand it (for analysis purposes)
4879         SendBoard(cps, moveNum + 1); // send position after move in stead.
4880         return;
4881     }
4882     if (cps->useUsermove) {
4883       SendToProgram("usermove ", cps);
4884     }
4885     if (cps->useSAN) {
4886       char *space;
4887       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4888         int len = space - parseList[moveNum];
4889         memcpy(buf, parseList[moveNum], len);
4890         buf[len++] = '\n';
4891         buf[len] = NULLCHAR;
4892       } else {
4893         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4894       }
4895       SendToProgram(buf, cps);
4896     } else {
4897       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4898         AlphaRank(moveList[moveNum], 4);
4899         SendToProgram(moveList[moveNum], cps);
4900         AlphaRank(moveList[moveNum], 4); // and back
4901       } else
4902       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4903        * the engine. It would be nice to have a better way to identify castle
4904        * moves here. */
4905       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4906                                                                          && cps->useOOCastle) {
4907         int fromX = moveList[moveNum][0] - AAA;
4908         int fromY = moveList[moveNum][1] - ONE;
4909         int toX = moveList[moveNum][2] - AAA;
4910         int toY = moveList[moveNum][3] - ONE;
4911         if((boards[moveNum][fromY][fromX] == WhiteKing
4912             && boards[moveNum][toY][toX] == WhiteRook)
4913            || (boards[moveNum][fromY][fromX] == BlackKing
4914                && boards[moveNum][toY][toX] == BlackRook)) {
4915           if(toX > fromX) SendToProgram("O-O\n", cps);
4916           else SendToProgram("O-O-O\n", cps);
4917         }
4918         else SendToProgram(moveList[moveNum], cps);
4919       } else
4920       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4921         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4922           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4923           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4924                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4925         } else
4926           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4927                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4928         SendToProgram(buf, cps);
4929       }
4930       else SendToProgram(moveList[moveNum], cps);
4931       /* End of additions by Tord */
4932     }
4933
4934     /* [HGM] setting up the opening has brought engine in force mode! */
4935     /*       Send 'go' if we are in a mode where machine should play. */
4936     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4937         (gameMode == TwoMachinesPlay   ||
4938 #if ZIPPY
4939          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4940 #endif
4941          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4942         SendToProgram("go\n", cps);
4943   if (appData.debugMode) {
4944     fprintf(debugFP, "(extra)\n");
4945   }
4946     }
4947     setboardSpoiledMachineBlack = 0;
4948 }
4949
4950 void
4951 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4952      ChessMove moveType;
4953      int fromX, fromY, toX, toY;
4954      char promoChar;
4955 {
4956     char user_move[MSG_SIZ];
4957     char suffix[4];
4958
4959     if(gameInfo.variant == VariantSChess && promoChar) {
4960         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4961         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4962     } else suffix[0] = NULLCHAR;
4963
4964     switch (moveType) {
4965       default:
4966         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4967                 (int)moveType, fromX, fromY, toX, toY);
4968         DisplayError(user_move + strlen("say "), 0);
4969         break;
4970       case WhiteKingSideCastle:
4971       case BlackKingSideCastle:
4972       case WhiteQueenSideCastleWild:
4973       case BlackQueenSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteHSideCastleFR:
4976       case BlackHSideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4979         break;
4980       case WhiteQueenSideCastle:
4981       case BlackQueenSideCastle:
4982       case WhiteKingSideCastleWild:
4983       case BlackKingSideCastleWild:
4984       /* PUSH Fabien */
4985       case WhiteASideCastleFR:
4986       case BlackASideCastleFR:
4987       /* POP Fabien */
4988         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4989         break;
4990       case WhiteNonPromotion:
4991       case BlackNonPromotion:
4992         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993         break;
4994       case WhitePromotion:
4995       case BlackPromotion:
4996         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteFerz));
5000         else if(gameInfo.variant == VariantGreat)
5001           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 PieceToChar(WhiteMan));
5004         else
5005           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5006                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5007                 promoChar);
5008         break;
5009       case WhiteDrop:
5010       case BlackDrop:
5011       drop:
5012         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5013                  ToUpper(PieceToChar((ChessSquare) fromX)),
5014                  AAA + toX, ONE + toY);
5015         break;
5016       case IllegalMove:  /* could be a variant we don't quite understand */
5017         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5018       case NormalMove:
5019       case WhiteCapturesEnPassant:
5020       case BlackCapturesEnPassant:
5021         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5023         break;
5024     }
5025     SendToICS(user_move);
5026     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5027         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5028 }
5029
5030 void
5031 UploadGameEvent()
5032 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5033     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5034     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5035     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5036       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5037       return;
5038     }
5039     if(gameMode != IcsExamining) { // is this ever not the case?
5040         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5041
5042         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5043           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5044         } else { // on FICS we must first go to general examine mode
5045           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5046         }
5047         if(gameInfo.variant != VariantNormal) {
5048             // try figure out wild number, as xboard names are not always valid on ICS
5049             for(i=1; i<=36; i++) {
5050               snprintf(buf, MSG_SIZ, "wild/%d", i);
5051                 if(StringToVariant(buf) == gameInfo.variant) break;
5052             }
5053             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5054             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5055             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5056         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5057         SendToICS(ics_prefix);
5058         SendToICS(buf);
5059         if(startedFromSetupPosition || backwardMostMove != 0) {
5060           fen = PositionToFEN(backwardMostMove, NULL);
5061           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5062             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5063             SendToICS(buf);
5064           } else { // FICS: everything has to set by separate bsetup commands
5065             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5066             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5067             SendToICS(buf);
5068             if(!WhiteOnMove(backwardMostMove)) {
5069                 SendToICS("bsetup tomove black\n");
5070             }
5071             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5072             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5073             SendToICS(buf);
5074             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5075             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5076             SendToICS(buf);
5077             i = boards[backwardMostMove][EP_STATUS];
5078             if(i >= 0) { // set e.p.
5079               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5080                 SendToICS(buf);
5081             }
5082             bsetup++;
5083           }
5084         }
5085       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5086             SendToICS("bsetup done\n"); // switch to normal examining.
5087     }
5088     for(i = backwardMostMove; i<last; i++) {
5089         char buf[20];
5090         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5091         SendToICS(buf);
5092     }
5093     SendToICS(ics_prefix);
5094     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5095 }
5096
5097 void
5098 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5099      int rf, ff, rt, ft;
5100      char promoChar;
5101      char move[7];
5102 {
5103     if (rf == DROP_RANK) {
5104       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5105       sprintf(move, "%c@%c%c\n",
5106                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5107     } else {
5108         if (promoChar == 'x' || promoChar == NULLCHAR) {
5109           sprintf(move, "%c%c%c%c\n",
5110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5111         } else {
5112             sprintf(move, "%c%c%c%c%c\n",
5113                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5114         }
5115     }
5116 }
5117
5118 void
5119 ProcessICSInitScript(f)
5120      FILE *f;
5121 {
5122     char buf[MSG_SIZ];
5123
5124     while (fgets(buf, MSG_SIZ, f)) {
5125         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5126     }
5127
5128     fclose(f);
5129 }
5130
5131
5132 static int lastX, lastY, selectFlag, dragging;
5133
5134 void
5135 Sweep(int step)
5136 {
5137     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5138     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5139     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5140     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5141     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5142     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5143     do {
5144         promoSweep -= step;
5145         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5146         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5147         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5148         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5149         if(!step) step = -1;
5150     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5151             appData.testLegality && (promoSweep == king ||
5152             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5153     ChangeDragPiece(promoSweep);
5154 }
5155
5156 int PromoScroll(int x, int y)
5157 {
5158   int step = 0;
5159
5160   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5161   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5162   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5163   if(!step) return FALSE;
5164   lastX = x; lastY = y;
5165   if((promoSweep < BlackPawn) == flipView) step = -step;
5166   if(step > 0) selectFlag = 1;
5167   if(!selectFlag) Sweep(step);
5168   return FALSE;
5169 }
5170
5171 void
5172 NextPiece(int step)
5173 {
5174     ChessSquare piece = boards[currentMove][toY][toX];
5175     do {
5176         pieceSweep -= step;
5177         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5178         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5179         if(!step) step = -1;
5180     } while(PieceToChar(pieceSweep) == '.');
5181     boards[currentMove][toY][toX] = pieceSweep;
5182     DrawPosition(FALSE, boards[currentMove]);
5183     boards[currentMove][toY][toX] = piece;
5184 }
5185 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5186 void
5187 AlphaRank(char *move, int n)
5188 {
5189 //    char *p = move, c; int x, y;
5190
5191     if (appData.debugMode) {
5192         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5193     }
5194
5195     if(move[1]=='*' &&
5196        move[2]>='0' && move[2]<='9' &&
5197        move[3]>='a' && move[3]<='x'    ) {
5198         move[1] = '@';
5199         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5201     } else
5202     if(move[0]>='0' && move[0]<='9' &&
5203        move[1]>='a' && move[1]<='x' &&
5204        move[2]>='0' && move[2]<='9' &&
5205        move[3]>='a' && move[3]<='x'    ) {
5206         /* input move, Shogi -> normal */
5207         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5208         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5209         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5210         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5211     } else
5212     if(move[1]=='@' &&
5213        move[3]>='0' && move[3]<='9' &&
5214        move[2]>='a' && move[2]<='x'    ) {
5215         move[1] = '*';
5216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5218     } else
5219     if(
5220        move[0]>='a' && move[0]<='x' &&
5221        move[3]>='0' && move[3]<='9' &&
5222        move[2]>='a' && move[2]<='x'    ) {
5223          /* output move, normal -> Shogi */
5224         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5225         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5226         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5227         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5228         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5229     }
5230     if (appData.debugMode) {
5231         fprintf(debugFP, "   out = '%s'\n", move);
5232     }
5233 }
5234
5235 char yy_textstr[8000];
5236
5237 /* Parser for moves from gnuchess, ICS, or user typein box */
5238 Boolean
5239 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5240      char *move;
5241      int moveNum;
5242      ChessMove *moveType;
5243      int *fromX, *fromY, *toX, *toY;
5244      char *promoChar;
5245 {
5246     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5247
5248     switch (*moveType) {
5249       case WhitePromotion:
5250       case BlackPromotion:
5251       case WhiteNonPromotion:
5252       case BlackNonPromotion:
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256       case WhiteKingSideCastle:
5257       case WhiteQueenSideCastle:
5258       case BlackKingSideCastle:
5259       case BlackQueenSideCastle:
5260       case WhiteKingSideCastleWild:
5261       case WhiteQueenSideCastleWild:
5262       case BlackKingSideCastleWild:
5263       case BlackQueenSideCastleWild:
5264       /* Code added by Tord: */
5265       case WhiteHSideCastleFR:
5266       case WhiteASideCastleFR:
5267       case BlackHSideCastleFR:
5268       case BlackASideCastleFR:
5269       /* End of code added by Tord */
5270       case IllegalMove:         /* bug or odd chess variant */
5271         *fromX = currentMoveString[0] - AAA;
5272         *fromY = currentMoveString[1] - ONE;
5273         *toX = currentMoveString[2] - AAA;
5274         *toY = currentMoveString[3] - ONE;
5275         *promoChar = currentMoveString[4];
5276         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5277             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5278     if (appData.debugMode) {
5279         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5280     }
5281             *fromX = *fromY = *toX = *toY = 0;
5282             return FALSE;
5283         }
5284         if (appData.testLegality) {
5285           return (*moveType != IllegalMove);
5286         } else {
5287           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5288                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5289         }
5290
5291       case WhiteDrop:
5292       case BlackDrop:
5293         *fromX = *moveType == WhiteDrop ?
5294           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5295           (int) CharToPiece(ToLower(currentMoveString[0]));
5296         *fromY = DROP_RANK;
5297         *toX = currentMoveString[2] - AAA;
5298         *toY = currentMoveString[3] - ONE;
5299         *promoChar = NULLCHAR;
5300         return TRUE;
5301
5302       case AmbiguousMove:
5303       case ImpossibleMove:
5304       case EndOfFile:
5305       case ElapsedTime:
5306       case Comment:
5307       case PGNTag:
5308       case NAG:
5309       case WhiteWins:
5310       case BlackWins:
5311       case GameIsDrawn:
5312       default:
5313     if (appData.debugMode) {
5314         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5315     }
5316         /* bug? */
5317         *fromX = *fromY = *toX = *toY = 0;
5318         *promoChar = NULLCHAR;
5319         return FALSE;
5320     }
5321 }
5322
5323 Boolean pushed = FALSE;
5324 char *lastParseAttempt;
5325
5326 void
5327 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5328 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5329   int fromX, fromY, toX, toY; char promoChar;
5330   ChessMove moveType;
5331   Boolean valid;
5332   int nr = 0;
5333
5334   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5335     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5336     pushed = TRUE;
5337   }
5338   endPV = forwardMostMove;
5339   do {
5340     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5341     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5342     lastParseAttempt = pv;
5343     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5344 if(appData.debugMode){
5345 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5346 }
5347     if(!valid && nr == 0 &&
5348        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5349         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5350         // Hande case where played move is different from leading PV move
5351         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5352         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5353         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5354         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5355           endPV += 2; // if position different, keep this
5356           moveList[endPV-1][0] = fromX + AAA;
5357           moveList[endPV-1][1] = fromY + ONE;
5358           moveList[endPV-1][2] = toX + AAA;
5359           moveList[endPV-1][3] = toY + ONE;
5360           parseList[endPV-1][0] = NULLCHAR;
5361           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5362         }
5363       }
5364     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5365     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5366     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5367     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5368         valid++; // allow comments in PV
5369         continue;
5370     }
5371     nr++;
5372     if(endPV+1 > framePtr) break; // no space, truncate
5373     if(!valid) break;
5374     endPV++;
5375     CopyBoard(boards[endPV], boards[endPV-1]);
5376     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5377     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5378     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5379     CoordsToAlgebraic(boards[endPV - 1],
5380                              PosFlags(endPV - 1),
5381                              fromY, fromX, toY, toX, promoChar,
5382                              parseList[endPV - 1]);
5383   } while(valid);
5384   if(atEnd == 2) return; // used hidden, for PV conversion
5385   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5386   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5387   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5388                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5389   DrawPosition(TRUE, boards[currentMove]);
5390 }
5391
5392 int
5393 MultiPV(ChessProgramState *cps)
5394 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5395         int i;
5396         for(i=0; i<cps->nrOptions; i++)
5397             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5398                 return i;
5399         return -1;
5400 }
5401
5402 Boolean
5403 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5404 {
5405         int startPV, multi, lineStart, origIndex = index;
5406         char *p, buf2[MSG_SIZ];
5407
5408         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5409         lastX = x; lastY = y;
5410         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5411         lineStart = startPV = index;
5412         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5413         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5414         index = startPV;
5415         do{ while(buf[index] && buf[index] != '\n') index++;
5416         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5417         buf[index] = 0;
5418         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5419                 int n = first.option[multi].value;
5420                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5421                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5422                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5423                 first.option[multi].value = n;
5424                 *start = *end = 0;
5425                 return FALSE;
5426         }
5427         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5428         *start = startPV; *end = index-1;
5429         return TRUE;
5430 }
5431
5432 char *
5433 PvToSAN(char *pv)
5434 {
5435         static char buf[10*MSG_SIZ];
5436         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5437         *buf = NULLCHAR;
5438         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5439         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5440         for(i = forwardMostMove; i<endPV; i++){
5441             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5442             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5443             k += strlen(buf+k);
5444         }
5445         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5446         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5447         endPV = savedEnd;
5448         return buf;
5449 }
5450
5451 Boolean
5452 LoadPV(int x, int y)
5453 { // called on right mouse click to load PV
5454   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5455   lastX = x; lastY = y;
5456   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5457   return TRUE;
5458 }
5459
5460 void
5461 UnLoadPV()
5462 {
5463   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5464   if(endPV < 0) return;
5465   endPV = -1;
5466   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5467         Boolean saveAnimate = appData.animate;
5468         if(pushed) {
5469             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5470                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5471             } else storedGames--; // abandon shelved tail of original game
5472         }
5473         pushed = FALSE;
5474         forwardMostMove = currentMove;
5475         currentMove = oldFMM;
5476         appData.animate = FALSE;
5477         ToNrEvent(forwardMostMove);
5478         appData.animate = saveAnimate;
5479   }
5480   currentMove = forwardMostMove;
5481   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5482   ClearPremoveHighlights();
5483   DrawPosition(TRUE, boards[currentMove]);
5484 }
5485
5486 void
5487 MovePV(int x, int y, int h)
5488 { // step through PV based on mouse coordinates (called on mouse move)
5489   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5490
5491   // we must somehow check if right button is still down (might be released off board!)
5492   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5493   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5494   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5495   if(!step) return;
5496   lastX = x; lastY = y;
5497
5498   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5499   if(endPV < 0) return;
5500   if(y < margin) step = 1; else
5501   if(y > h - margin) step = -1;
5502   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5503   currentMove += step;
5504   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5505   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5506                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5507   DrawPosition(FALSE, boards[currentMove]);
5508 }
5509
5510
5511 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5512 // All positions will have equal probability, but the current method will not provide a unique
5513 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5514 #define DARK 1
5515 #define LITE 2
5516 #define ANY 3
5517
5518 int squaresLeft[4];
5519 int piecesLeft[(int)BlackPawn];
5520 int seed, nrOfShuffles;
5521
5522 void GetPositionNumber()
5523 {       // sets global variable seed
5524         int i;
5525
5526         seed = appData.defaultFrcPosition;
5527         if(seed < 0) { // randomize based on time for negative FRC position numbers
5528                 for(i=0; i<50; i++) seed += random();
5529                 seed = random() ^ random() >> 8 ^ random() << 8;
5530                 if(seed<0) seed = -seed;
5531         }
5532 }
5533
5534 int put(Board board, int pieceType, int rank, int n, int shade)
5535 // put the piece on the (n-1)-th empty squares of the given shade
5536 {
5537         int i;
5538
5539         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5540                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5541                         board[rank][i] = (ChessSquare) pieceType;
5542                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5543                         squaresLeft[ANY]--;
5544                         piecesLeft[pieceType]--;
5545                         return i;
5546                 }
5547         }
5548         return -1;
5549 }
5550
5551
5552 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5553 // calculate where the next piece goes, (any empty square), and put it there
5554 {
5555         int i;
5556
5557         i = seed % squaresLeft[shade];
5558         nrOfShuffles *= squaresLeft[shade];
5559         seed /= squaresLeft[shade];
5560         put(board, pieceType, rank, i, shade);
5561 }
5562
5563 void AddTwoPieces(Board board, int pieceType, int rank)
5564 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5565 {
5566         int i, n=squaresLeft[ANY], j=n-1, k;
5567
5568         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5569         i = seed % k;  // pick one
5570         nrOfShuffles *= k;
5571         seed /= k;
5572         while(i >= j) i -= j--;
5573         j = n - 1 - j; i += j;
5574         put(board, pieceType, rank, j, ANY);
5575         put(board, pieceType, rank, i, ANY);
5576 }
5577
5578 void SetUpShuffle(Board board, int number)
5579 {
5580         int i, p, first=1;
5581
5582         GetPositionNumber(); nrOfShuffles = 1;
5583
5584         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5585         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5586         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5587
5588         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5589
5590         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5591             p = (int) board[0][i];
5592             if(p < (int) BlackPawn) piecesLeft[p] ++;
5593             board[0][i] = EmptySquare;
5594         }
5595
5596         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5597             // shuffles restricted to allow normal castling put KRR first
5598             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5599                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5600             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5601                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5602             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5603                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5604             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5605                 put(board, WhiteRook, 0, 0, ANY);
5606             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5607         }
5608
5609         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5610             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5611             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5612                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5613                 while(piecesLeft[p] >= 2) {
5614                     AddOnePiece(board, p, 0, LITE);
5615                     AddOnePiece(board, p, 0, DARK);
5616                 }
5617                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5618             }
5619
5620         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5621             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5622             // but we leave King and Rooks for last, to possibly obey FRC restriction
5623             if(p == (int)WhiteRook) continue;
5624             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5625             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5626         }
5627
5628         // now everything is placed, except perhaps King (Unicorn) and Rooks
5629
5630         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5631             // Last King gets castling rights
5632             while(piecesLeft[(int)WhiteUnicorn]) {
5633                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5634                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5635             }
5636
5637             while(piecesLeft[(int)WhiteKing]) {
5638                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5639                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5640             }
5641
5642
5643         } else {
5644             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5645             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5646         }
5647
5648         // Only Rooks can be left; simply place them all
5649         while(piecesLeft[(int)WhiteRook]) {
5650                 i = put(board, WhiteRook, 0, 0, ANY);
5651                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5652                         if(first) {
5653                                 first=0;
5654                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5655                         }
5656                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5657                 }
5658         }
5659         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5660             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5661         }
5662
5663         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5664 }
5665
5666 int SetCharTable( char *table, const char * map )
5667 /* [HGM] moved here from winboard.c because of its general usefulness */
5668 /*       Basically a safe strcpy that uses the last character as King */
5669 {
5670     int result = FALSE; int NrPieces;
5671
5672     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5673                     && NrPieces >= 12 && !(NrPieces&1)) {
5674         int i; /* [HGM] Accept even length from 12 to 34 */
5675
5676         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5677         for( i=0; i<NrPieces/2-1; i++ ) {
5678             table[i] = map[i];
5679             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5680         }
5681         table[(int) WhiteKing]  = map[NrPieces/2-1];
5682         table[(int) BlackKing]  = map[NrPieces-1];
5683
5684         result = TRUE;
5685     }
5686
5687     return result;
5688 }
5689
5690 void Prelude(Board board)
5691 {       // [HGM] superchess: random selection of exo-pieces
5692         int i, j, k; ChessSquare p;
5693         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5694
5695         GetPositionNumber(); // use FRC position number
5696
5697         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5698             SetCharTable(pieceToChar, appData.pieceToCharTable);
5699             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5700                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5701         }
5702
5703         j = seed%4;                 seed /= 4;
5704         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%3 + (seed%3 >= j); seed /= 3;
5708         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%3;                 seed /= 3;
5712         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5713         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5714         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5715         j = seed%2 + (seed%2 >= j); seed /= 2;
5716         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5717         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5718         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5719         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5720         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5721         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5722         put(board, exoPieces[0],    0, 0, ANY);
5723         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5724 }
5725
5726 void
5727 InitPosition(redraw)
5728      int redraw;
5729 {
5730     ChessSquare (* pieces)[BOARD_FILES];
5731     int i, j, pawnRow, overrule,
5732     oldx = gameInfo.boardWidth,
5733     oldy = gameInfo.boardHeight,
5734     oldh = gameInfo.holdingsWidth;
5735     static int oldv;
5736
5737     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5738
5739     /* [AS] Initialize pv info list [HGM] and game status */
5740     {
5741         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5742             pvInfoList[i].depth = 0;
5743             boards[i][EP_STATUS] = EP_NONE;
5744             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5745         }
5746
5747         initialRulePlies = 0; /* 50-move counter start */
5748
5749         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5750         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5751     }
5752
5753
5754     /* [HGM] logic here is completely changed. In stead of full positions */
5755     /* the initialized data only consist of the two backranks. The switch */
5756     /* selects which one we will use, which is than copied to the Board   */
5757     /* initialPosition, which for the rest is initialized by Pawns and    */
5758     /* empty squares. This initial position is then copied to boards[0],  */
5759     /* possibly after shuffling, so that it remains available.            */
5760
5761     gameInfo.holdingsWidth = 0; /* default board sizes */
5762     gameInfo.boardWidth    = 8;
5763     gameInfo.boardHeight   = 8;
5764     gameInfo.holdingsSize  = 0;
5765     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5766     for(i=0; i<BOARD_FILES-2; i++)
5767       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5768     initialPosition[EP_STATUS] = EP_NONE;
5769     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5770     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5771          SetCharTable(pieceNickName, appData.pieceNickNames);
5772     else SetCharTable(pieceNickName, "............");
5773     pieces = FIDEArray;
5774
5775     switch (gameInfo.variant) {
5776     case VariantFischeRandom:
5777       shuffleOpenings = TRUE;
5778     default:
5779       break;
5780     case VariantShatranj:
5781       pieces = ShatranjArray;
5782       nrCastlingRights = 0;
5783       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5784       break;
5785     case VariantMakruk:
5786       pieces = makrukArray;
5787       nrCastlingRights = 0;
5788       startedFromSetupPosition = TRUE;
5789       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5790       break;
5791     case VariantTwoKings:
5792       pieces = twoKingsArray;
5793       break;
5794     case VariantGrand:
5795       pieces = GrandArray;
5796       nrCastlingRights = 0;
5797       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5798       gameInfo.boardWidth = 10;
5799       gameInfo.boardHeight = 10;
5800       gameInfo.holdingsSize = 7;
5801       break;
5802     case VariantCapaRandom:
5803       shuffleOpenings = TRUE;
5804     case VariantCapablanca:
5805       pieces = CapablancaArray;
5806       gameInfo.boardWidth = 10;
5807       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5808       break;
5809     case VariantGothic:
5810       pieces = GothicArray;
5811       gameInfo.boardWidth = 10;
5812       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5813       break;
5814     case VariantSChess:
5815       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5816       gameInfo.holdingsSize = 7;
5817       break;
5818     case VariantJanus:
5819       pieces = JanusArray;
5820       gameInfo.boardWidth = 10;
5821       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5822       nrCastlingRights = 6;
5823         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5824         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5825         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5826         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5827         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5828         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5829       break;
5830     case VariantFalcon:
5831       pieces = FalconArray;
5832       gameInfo.boardWidth = 10;
5833       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5834       break;
5835     case VariantXiangqi:
5836       pieces = XiangqiArray;
5837       gameInfo.boardWidth  = 9;
5838       gameInfo.boardHeight = 10;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5841       break;
5842     case VariantShogi:
5843       pieces = ShogiArray;
5844       gameInfo.boardWidth  = 9;
5845       gameInfo.boardHeight = 9;
5846       gameInfo.holdingsSize = 7;
5847       nrCastlingRights = 0;
5848       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5849       break;
5850     case VariantCourier:
5851       pieces = CourierArray;
5852       gameInfo.boardWidth  = 12;
5853       nrCastlingRights = 0;
5854       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5855       break;
5856     case VariantKnightmate:
5857       pieces = KnightmateArray;
5858       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5859       break;
5860     case VariantSpartan:
5861       pieces = SpartanArray;
5862       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5863       break;
5864     case VariantFairy:
5865       pieces = fairyArray;
5866       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5867       break;
5868     case VariantGreat:
5869       pieces = GreatArray;
5870       gameInfo.boardWidth = 10;
5871       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5872       gameInfo.holdingsSize = 8;
5873       break;
5874     case VariantSuper:
5875       pieces = FIDEArray;
5876       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5877       gameInfo.holdingsSize = 8;
5878       startedFromSetupPosition = TRUE;
5879       break;
5880     case VariantCrazyhouse:
5881     case VariantBughouse:
5882       pieces = FIDEArray;
5883       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5884       gameInfo.holdingsSize = 5;
5885       break;
5886     case VariantWildCastle:
5887       pieces = FIDEArray;
5888       /* !!?shuffle with kings guaranteed to be on d or e file */
5889       shuffleOpenings = 1;
5890       break;
5891     case VariantNoCastle:
5892       pieces = FIDEArray;
5893       nrCastlingRights = 0;
5894       /* !!?unconstrained back-rank shuffle */
5895       shuffleOpenings = 1;
5896       break;
5897     }
5898
5899     overrule = 0;
5900     if(appData.NrFiles >= 0) {
5901         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5902         gameInfo.boardWidth = appData.NrFiles;
5903     }
5904     if(appData.NrRanks >= 0) {
5905         gameInfo.boardHeight = appData.NrRanks;
5906     }
5907     if(appData.holdingsSize >= 0) {
5908         i = appData.holdingsSize;
5909         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5910         gameInfo.holdingsSize = i;
5911     }
5912     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5913     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5914         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5915
5916     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5917     if(pawnRow < 1) pawnRow = 1;
5918     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5919
5920     /* User pieceToChar list overrules defaults */
5921     if(appData.pieceToCharTable != NULL)
5922         SetCharTable(pieceToChar, appData.pieceToCharTable);
5923
5924     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5925
5926         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5927             s = (ChessSquare) 0; /* account holding counts in guard band */
5928         for( i=0; i<BOARD_HEIGHT; i++ )
5929             initialPosition[i][j] = s;
5930
5931         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5932         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5933         initialPosition[pawnRow][j] = WhitePawn;
5934         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5935         if(gameInfo.variant == VariantXiangqi) {
5936             if(j&1) {
5937                 initialPosition[pawnRow][j] =
5938                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5939                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5940                    initialPosition[2][j] = WhiteCannon;
5941                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5942                 }
5943             }
5944         }
5945         if(gameInfo.variant == VariantGrand) {
5946             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5947                initialPosition[0][j] = WhiteRook;
5948                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5949             }
5950         }
5951         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5952     }
5953     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5954
5955             j=BOARD_LEFT+1;
5956             initialPosition[1][j] = WhiteBishop;
5957             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5958             j=BOARD_RGHT-2;
5959             initialPosition[1][j] = WhiteRook;
5960             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5961     }
5962
5963     if( nrCastlingRights == -1) {
5964         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5965         /*       This sets default castling rights from none to normal corners   */
5966         /* Variants with other castling rights must set them themselves above    */
5967         nrCastlingRights = 6;
5968
5969         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5970         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5971         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5972         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5973         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5974         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5975      }
5976
5977      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5978      if(gameInfo.variant == VariantGreat) { // promotion commoners
5979         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5981         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5983      }
5984      if( gameInfo.variant == VariantSChess ) {
5985       initialPosition[1][0] = BlackMarshall;
5986       initialPosition[2][0] = BlackAngel;
5987       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5988       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5989       initialPosition[1][1] = initialPosition[2][1] = 
5990       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5991      }
5992   if (appData.debugMode) {
5993     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5994   }
5995     if(shuffleOpenings) {
5996         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5997         startedFromSetupPosition = TRUE;
5998     }
5999     if(startedFromPositionFile) {
6000       /* [HGM] loadPos: use PositionFile for every new game */
6001       CopyBoard(initialPosition, filePosition);
6002       for(i=0; i<nrCastlingRights; i++)
6003           initialRights[i] = filePosition[CASTLING][i];
6004       startedFromSetupPosition = TRUE;
6005     }
6006
6007     CopyBoard(boards[0], initialPosition);
6008
6009     if(oldx != gameInfo.boardWidth ||
6010        oldy != gameInfo.boardHeight ||
6011        oldv != gameInfo.variant ||
6012        oldh != gameInfo.holdingsWidth
6013                                          )
6014             InitDrawingSizes(-2 ,0);
6015
6016     oldv = gameInfo.variant;
6017     if (redraw)
6018       DrawPosition(TRUE, boards[currentMove]);
6019 }
6020
6021 void
6022 SendBoard(cps, moveNum)
6023      ChessProgramState *cps;
6024      int moveNum;
6025 {
6026     char message[MSG_SIZ];
6027
6028     if (cps->useSetboard) {
6029       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6030       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6031       SendToProgram(message, cps);
6032       free(fen);
6033
6034     } else {
6035       ChessSquare *bp;
6036       int i, j, left=0, right=BOARD_WIDTH;
6037       /* Kludge to set black to move, avoiding the troublesome and now
6038        * deprecated "black" command.
6039        */
6040       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6041         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6042
6043       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6044
6045       SendToProgram("edit\n", cps);
6046       SendToProgram("#\n", cps);
6047       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6048         bp = &boards[moveNum][i][left];
6049         for (j = left; j < right; j++, bp++) {
6050           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6051           if ((int) *bp < (int) BlackPawn) {
6052             if(j == BOARD_RGHT+1)
6053                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6054             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6055             if(message[0] == '+' || message[0] == '~') {
6056               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6057                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6058                         AAA + j, ONE + i);
6059             }
6060             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6061                 message[1] = BOARD_RGHT   - 1 - j + '1';
6062                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6063             }
6064             SendToProgram(message, cps);
6065           }
6066         }
6067       }
6068
6069       SendToProgram("c\n", cps);
6070       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6071         bp = &boards[moveNum][i][left];
6072         for (j = left; j < right; j++, bp++) {
6073           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6074           if (((int) *bp != (int) EmptySquare)
6075               && ((int) *bp >= (int) BlackPawn)) {
6076             if(j == BOARD_LEFT-2)
6077                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6078             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6079                     AAA + j, ONE + i);
6080             if(message[0] == '+' || message[0] == '~') {
6081               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6082                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6083                         AAA + j, ONE + i);
6084             }
6085             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6086                 message[1] = BOARD_RGHT   - 1 - j + '1';
6087                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6088             }
6089             SendToProgram(message, cps);
6090           }
6091         }
6092       }
6093
6094       SendToProgram(".\n", cps);
6095     }
6096     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6097 }
6098
6099 ChessSquare
6100 DefaultPromoChoice(int white)
6101 {
6102     ChessSquare result;
6103     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6104         result = WhiteFerz; // no choice
6105     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6106         result= WhiteKing; // in Suicide Q is the last thing we want
6107     else if(gameInfo.variant == VariantSpartan)
6108         result = white ? WhiteQueen : WhiteAngel;
6109     else result = WhiteQueen;
6110     if(!white) result = WHITE_TO_BLACK result;
6111     return result;
6112 }
6113
6114 static int autoQueen; // [HGM] oneclick
6115
6116 int
6117 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6118 {
6119     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6120     /* [HGM] add Shogi promotions */
6121     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6122     ChessSquare piece;
6123     ChessMove moveType;
6124     Boolean premove;
6125
6126     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6127     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6128
6129     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6130       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6131         return FALSE;
6132
6133     piece = boards[currentMove][fromY][fromX];
6134     if(gameInfo.variant == VariantShogi) {
6135         promotionZoneSize = BOARD_HEIGHT/3;
6136         highestPromotingPiece = (int)WhiteFerz;
6137     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6138         promotionZoneSize = 3;
6139     }
6140
6141     // Treat Lance as Pawn when it is not representing Amazon
6142     if(gameInfo.variant != VariantSuper) {
6143         if(piece == WhiteLance) piece = WhitePawn; else
6144         if(piece == BlackLance) piece = BlackPawn;
6145     }
6146
6147     // next weed out all moves that do not touch the promotion zone at all
6148     if((int)piece >= BlackPawn) {
6149         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6150              return FALSE;
6151         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6152     } else {
6153         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6154            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6155     }
6156
6157     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6158
6159     // weed out mandatory Shogi promotions
6160     if(gameInfo.variant == VariantShogi) {
6161         if(piece >= BlackPawn) {
6162             if(toY == 0 && piece == BlackPawn ||
6163                toY == 0 && piece == BlackQueen ||
6164                toY <= 1 && piece == BlackKnight) {
6165                 *promoChoice = '+';
6166                 return FALSE;
6167             }
6168         } else {
6169             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6170                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6171                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6172                 *promoChoice = '+';
6173                 return FALSE;
6174             }
6175         }
6176     }
6177
6178     // weed out obviously illegal Pawn moves
6179     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6180         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6181         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6182         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6183         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6184         // note we are not allowed to test for valid (non-)capture, due to premove
6185     }
6186
6187     // we either have a choice what to promote to, or (in Shogi) whether to promote
6188     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6189         *promoChoice = PieceToChar(BlackFerz);  // no choice
6190         return FALSE;
6191     }
6192     // no sense asking what we must promote to if it is going to explode...
6193     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6194         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6195         return FALSE;
6196     }
6197     // give caller the default choice even if we will not make it
6198     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6199     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6200     if(        sweepSelect && gameInfo.variant != VariantGreat
6201                            && gameInfo.variant != VariantGrand
6202                            && gameInfo.variant != VariantSuper) return FALSE;
6203     if(autoQueen) return FALSE; // predetermined
6204
6205     // suppress promotion popup on illegal moves that are not premoves
6206     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6207               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6208     if(appData.testLegality && !premove) {
6209         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6210                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6211         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6212             return FALSE;
6213     }
6214
6215     return TRUE;
6216 }
6217
6218 int
6219 InPalace(row, column)
6220      int row, column;
6221 {   /* [HGM] for Xiangqi */
6222     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6223          column < (BOARD_WIDTH + 4)/2 &&
6224          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6225     return FALSE;
6226 }
6227
6228 int
6229 PieceForSquare (x, y)
6230      int x;
6231      int y;
6232 {
6233   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6234      return -1;
6235   else
6236      return boards[currentMove][y][x];
6237 }
6238
6239 int
6240 OKToStartUserMove(x, y)
6241      int x, y;
6242 {
6243     ChessSquare from_piece;
6244     int white_piece;
6245
6246     if (matchMode) return FALSE;
6247     if (gameMode == EditPosition) return TRUE;
6248
6249     if (x >= 0 && y >= 0)
6250       from_piece = boards[currentMove][y][x];
6251     else
6252       from_piece = EmptySquare;
6253
6254     if (from_piece == EmptySquare) return FALSE;
6255
6256     white_piece = (int)from_piece >= (int)WhitePawn &&
6257       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6258
6259     switch (gameMode) {
6260       case AnalyzeFile:
6261       case TwoMachinesPlay:
6262       case EndOfGame:
6263         return FALSE;
6264
6265       case IcsObserving:
6266       case IcsIdle:
6267         return FALSE;
6268
6269       case MachinePlaysWhite:
6270       case IcsPlayingBlack:
6271         if (appData.zippyPlay) return FALSE;
6272         if (white_piece) {
6273             DisplayMoveError(_("You are playing Black"));
6274             return FALSE;
6275         }
6276         break;
6277
6278       case MachinePlaysBlack:
6279       case IcsPlayingWhite:
6280         if (appData.zippyPlay) return FALSE;
6281         if (!white_piece) {
6282             DisplayMoveError(_("You are playing White"));
6283             return FALSE;
6284         }
6285         break;
6286
6287       case PlayFromGameFile:
6288             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6289       case EditGame:
6290         if (!white_piece && WhiteOnMove(currentMove)) {
6291             DisplayMoveError(_("It is White's turn"));
6292             return FALSE;
6293         }
6294         if (white_piece && !WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is Black's turn"));
6296             return FALSE;
6297         }
6298         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6299             /* Editing correspondence game history */
6300             /* Could disallow this or prompt for confirmation */
6301             cmailOldMove = -1;
6302         }
6303         break;
6304
6305       case BeginningOfGame:
6306         if (appData.icsActive) return FALSE;
6307         if (!appData.noChessProgram) {
6308             if (!white_piece) {
6309                 DisplayMoveError(_("You are playing White"));
6310                 return FALSE;
6311             }
6312         }
6313         break;
6314
6315       case Training:
6316         if (!white_piece && WhiteOnMove(currentMove)) {
6317             DisplayMoveError(_("It is White's turn"));
6318             return FALSE;
6319         }
6320         if (white_piece && !WhiteOnMove(currentMove)) {
6321             DisplayMoveError(_("It is Black's turn"));
6322             return FALSE;
6323         }
6324         break;
6325
6326       default:
6327       case IcsExamining:
6328         break;
6329     }
6330     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6331         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6332         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6333         && gameMode != AnalyzeFile && gameMode != Training) {
6334         DisplayMoveError(_("Displayed position is not current"));
6335         return FALSE;
6336     }
6337     return TRUE;
6338 }
6339
6340 Boolean
6341 OnlyMove(int *x, int *y, Boolean captures) {
6342     DisambiguateClosure cl;
6343     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6344     switch(gameMode) {
6345       case MachinePlaysBlack:
6346       case IcsPlayingWhite:
6347       case BeginningOfGame:
6348         if(!WhiteOnMove(currentMove)) return FALSE;
6349         break;
6350       case MachinePlaysWhite:
6351       case IcsPlayingBlack:
6352         if(WhiteOnMove(currentMove)) return FALSE;
6353         break;
6354       case EditGame:
6355         break;
6356       default:
6357         return FALSE;
6358     }
6359     cl.pieceIn = EmptySquare;
6360     cl.rfIn = *y;
6361     cl.ffIn = *x;
6362     cl.rtIn = -1;
6363     cl.ftIn = -1;
6364     cl.promoCharIn = NULLCHAR;
6365     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6366     if( cl.kind == NormalMove ||
6367         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6368         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6369         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6370       fromX = cl.ff;
6371       fromY = cl.rf;
6372       *x = cl.ft;
6373       *y = cl.rt;
6374       return TRUE;
6375     }
6376     if(cl.kind != ImpossibleMove) return FALSE;
6377     cl.pieceIn = EmptySquare;
6378     cl.rfIn = -1;
6379     cl.ffIn = -1;
6380     cl.rtIn = *y;
6381     cl.ftIn = *x;
6382     cl.promoCharIn = NULLCHAR;
6383     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6384     if( cl.kind == NormalMove ||
6385         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6386         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6387         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6388       fromX = cl.ff;
6389       fromY = cl.rf;
6390       *x = cl.ft;
6391       *y = cl.rt;
6392       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6393       return TRUE;
6394     }
6395     return FALSE;
6396 }
6397
6398 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6399 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6400 int lastLoadGameUseList = FALSE;
6401 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6402 ChessMove lastLoadGameStart = EndOfFile;
6403
6404 void
6405 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6406      int fromX, fromY, toX, toY;
6407      int promoChar;
6408 {
6409     ChessMove moveType;
6410     ChessSquare pdown, pup;
6411
6412     /* Check if the user is playing in turn.  This is complicated because we
6413        let the user "pick up" a piece before it is his turn.  So the piece he
6414        tried to pick up may have been captured by the time he puts it down!
6415        Therefore we use the color the user is supposed to be playing in this
6416        test, not the color of the piece that is currently on the starting
6417        square---except in EditGame mode, where the user is playing both
6418        sides; fortunately there the capture race can't happen.  (It can
6419        now happen in IcsExamining mode, but that's just too bad.  The user
6420        will get a somewhat confusing message in that case.)
6421        */
6422
6423     switch (gameMode) {
6424       case AnalyzeFile:
6425       case TwoMachinesPlay:
6426       case EndOfGame:
6427       case IcsObserving:
6428       case IcsIdle:
6429         /* We switched into a game mode where moves are not accepted,
6430            perhaps while the mouse button was down. */
6431         return;
6432
6433       case MachinePlaysWhite:
6434         /* User is moving for Black */
6435         if (WhiteOnMove(currentMove)) {
6436             DisplayMoveError(_("It is White's turn"));
6437             return;
6438         }
6439         break;
6440
6441       case MachinePlaysBlack:
6442         /* User is moving for White */
6443         if (!WhiteOnMove(currentMove)) {
6444             DisplayMoveError(_("It is Black's turn"));
6445             return;
6446         }
6447         break;
6448
6449       case PlayFromGameFile:
6450             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6451       case EditGame:
6452       case IcsExamining:
6453       case BeginningOfGame:
6454       case AnalyzeMode:
6455       case Training:
6456         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6457         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6458             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6459             /* User is moving for Black */
6460             if (WhiteOnMove(currentMove)) {
6461                 DisplayMoveError(_("It is White's turn"));
6462                 return;
6463             }
6464         } else {
6465             /* User is moving for White */
6466             if (!WhiteOnMove(currentMove)) {
6467                 DisplayMoveError(_("It is Black's turn"));
6468                 return;
6469             }
6470         }
6471         break;
6472
6473       case IcsPlayingBlack:
6474         /* User is moving for Black */
6475         if (WhiteOnMove(currentMove)) {
6476             if (!appData.premove) {
6477                 DisplayMoveError(_("It is White's turn"));
6478             } else if (toX >= 0 && toY >= 0) {
6479                 premoveToX = toX;
6480                 premoveToY = toY;
6481                 premoveFromX = fromX;
6482                 premoveFromY = fromY;
6483                 premovePromoChar = promoChar;
6484                 gotPremove = 1;
6485                 if (appData.debugMode)
6486                     fprintf(debugFP, "Got premove: fromX %d,"
6487                             "fromY %d, toX %d, toY %d\n",
6488                             fromX, fromY, toX, toY);
6489             }
6490             return;
6491         }
6492         break;
6493
6494       case IcsPlayingWhite:
6495         /* User is moving for White */
6496         if (!WhiteOnMove(currentMove)) {
6497             if (!appData.premove) {
6498                 DisplayMoveError(_("It is Black's turn"));
6499             } else if (toX >= 0 && toY >= 0) {
6500                 premoveToX = toX;
6501                 premoveToY = toY;
6502                 premoveFromX = fromX;
6503                 premoveFromY = fromY;
6504                 premovePromoChar = promoChar;
6505                 gotPremove = 1;
6506                 if (appData.debugMode)
6507                     fprintf(debugFP, "Got premove: fromX %d,"
6508                             "fromY %d, toX %d, toY %d\n",
6509                             fromX, fromY, toX, toY);
6510             }
6511             return;
6512         }
6513         break;
6514
6515       default:
6516         break;
6517
6518       case EditPosition:
6519         /* EditPosition, empty square, or different color piece;
6520            click-click move is possible */
6521         if (toX == -2 || toY == -2) {
6522             boards[0][fromY][fromX] = EmptySquare;
6523             DrawPosition(FALSE, boards[currentMove]);
6524             return;
6525         } else if (toX >= 0 && toY >= 0) {
6526             boards[0][toY][toX] = boards[0][fromY][fromX];
6527             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6528                 if(boards[0][fromY][0] != EmptySquare) {
6529                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6530                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6531                 }
6532             } else
6533             if(fromX == BOARD_RGHT+1) {
6534                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6535                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6536                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6537                 }
6538             } else
6539             boards[0][fromY][fromX] = EmptySquare;
6540             DrawPosition(FALSE, boards[currentMove]);
6541             return;
6542         }
6543         return;
6544     }
6545
6546     if(toX < 0 || toY < 0) return;
6547     pdown = boards[currentMove][fromY][fromX];
6548     pup = boards[currentMove][toY][toX];
6549
6550     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6551     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6552          if( pup != EmptySquare ) return;
6553          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6554            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6555                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6556            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6557            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6558            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6559            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6560          fromY = DROP_RANK;
6561     }
6562
6563     /* [HGM] always test for legality, to get promotion info */
6564     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6565                                          fromY, fromX, toY, toX, promoChar);
6566
6567     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6568
6569     /* [HGM] but possibly ignore an IllegalMove result */
6570     if (appData.testLegality) {
6571         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6572             DisplayMoveError(_("Illegal move"));
6573             return;
6574         }
6575     }
6576
6577     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6578 }
6579
6580 /* Common tail of UserMoveEvent and DropMenuEvent */
6581 int
6582 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6583      ChessMove moveType;
6584      int fromX, fromY, toX, toY;
6585      /*char*/int promoChar;
6586 {
6587     char *bookHit = 0;
6588
6589     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6590         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6591         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6592         if(WhiteOnMove(currentMove)) {
6593             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6594         } else {
6595             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6596         }
6597     }
6598
6599     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6600        move type in caller when we know the move is a legal promotion */
6601     if(moveType == NormalMove && promoChar)
6602         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6603
6604     /* [HGM] <popupFix> The following if has been moved here from
6605        UserMoveEvent(). Because it seemed to belong here (why not allow
6606        piece drops in training games?), and because it can only be
6607        performed after it is known to what we promote. */
6608     if (gameMode == Training) {
6609       /* compare the move played on the board to the next move in the
6610        * game. If they match, display the move and the opponent's response.
6611        * If they don't match, display an error message.
6612        */
6613       int saveAnimate;
6614       Board testBoard;
6615       CopyBoard(testBoard, boards[currentMove]);
6616       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6617
6618       if (CompareBoards(testBoard, boards[currentMove+1])) {
6619         ForwardInner(currentMove+1);
6620
6621         /* Autoplay the opponent's response.
6622          * if appData.animate was TRUE when Training mode was entered,
6623          * the response will be animated.
6624          */
6625         saveAnimate = appData.animate;
6626         appData.animate = animateTraining;
6627         ForwardInner(currentMove+1);
6628         appData.animate = saveAnimate;
6629
6630         /* check for the end of the game */
6631         if (currentMove >= forwardMostMove) {
6632           gameMode = PlayFromGameFile;
6633           ModeHighlight();
6634           SetTrainingModeOff();
6635           DisplayInformation(_("End of game"));
6636         }
6637       } else {
6638         DisplayError(_("Incorrect move"), 0);
6639       }
6640       return 1;
6641     }
6642
6643   /* Ok, now we know that the move is good, so we can kill
6644      the previous line in Analysis Mode */
6645   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6646                                 && currentMove < forwardMostMove) {
6647     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6648     else forwardMostMove = currentMove;
6649   }
6650
6651   /* If we need the chess program but it's dead, restart it */
6652   ResurrectChessProgram();
6653
6654   /* A user move restarts a paused game*/
6655   if (pausing)
6656     PauseEvent();
6657
6658   thinkOutput[0] = NULLCHAR;
6659
6660   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6661
6662   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6663     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6664     return 1;
6665   }
6666
6667   if (gameMode == BeginningOfGame) {
6668     if (appData.noChessProgram) {
6669       gameMode = EditGame;
6670       SetGameInfo();
6671     } else {
6672       char buf[MSG_SIZ];
6673       gameMode = MachinePlaysBlack;
6674       StartClocks();
6675       SetGameInfo();
6676       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6677       DisplayTitle(buf);
6678       if (first.sendName) {
6679         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6680         SendToProgram(buf, &first);
6681       }
6682       StartClocks();
6683     }
6684     ModeHighlight();
6685   }
6686
6687   /* Relay move to ICS or chess engine */
6688   if (appData.icsActive) {
6689     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6690         gameMode == IcsExamining) {
6691       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6692         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6693         SendToICS("draw ");
6694         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6695       }
6696       // also send plain move, in case ICS does not understand atomic claims
6697       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6698       ics_user_moved = 1;
6699     }
6700   } else {
6701     if (first.sendTime && (gameMode == BeginningOfGame ||
6702                            gameMode == MachinePlaysWhite ||
6703                            gameMode == MachinePlaysBlack)) {
6704       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6705     }
6706     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6707          // [HGM] book: if program might be playing, let it use book
6708         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6709         first.maybeThinking = TRUE;
6710     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6711         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6712         SendBoard(&first, currentMove+1);
6713     } else SendMoveToProgram(forwardMostMove-1, &first);
6714     if (currentMove == cmailOldMove + 1) {
6715       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6716     }
6717   }
6718
6719   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6720
6721   switch (gameMode) {
6722   case EditGame:
6723     if(appData.testLegality)
6724     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6725     case MT_NONE:
6726     case MT_CHECK:
6727       break;
6728     case MT_CHECKMATE:
6729     case MT_STAINMATE:
6730       if (WhiteOnMove(currentMove)) {
6731         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6732       } else {
6733         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6734       }
6735       break;
6736     case MT_STALEMATE:
6737       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6738       break;
6739     }
6740     break;
6741
6742   case MachinePlaysBlack:
6743   case MachinePlaysWhite:
6744     /* disable certain menu options while machine is thinking */
6745     SetMachineThinkingEnables();
6746     break;
6747
6748   default:
6749     break;
6750   }
6751
6752   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6753   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6754
6755   if(bookHit) { // [HGM] book: simulate book reply
6756         static char bookMove[MSG_SIZ]; // a bit generous?
6757
6758         programStats.nodes = programStats.depth = programStats.time =
6759         programStats.score = programStats.got_only_move = 0;
6760         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6761
6762         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6763         strcat(bookMove, bookHit);
6764         HandleMachineMove(bookMove, &first);
6765   }
6766   return 1;
6767 }
6768
6769 void
6770 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6771      Board board;
6772      int flags;
6773      ChessMove kind;
6774      int rf, ff, rt, ft;
6775      VOIDSTAR closure;
6776 {
6777     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6778     Markers *m = (Markers *) closure;
6779     if(rf == fromY && ff == fromX)
6780         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6781                          || kind == WhiteCapturesEnPassant
6782                          || kind == BlackCapturesEnPassant);
6783     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6784 }
6785
6786 void
6787 MarkTargetSquares(int clear)
6788 {
6789   int x, y;
6790   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6791      !appData.testLegality || gameMode == EditPosition) return;
6792   if(clear) {
6793     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6794   } else {
6795     int capt = 0;
6796     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6797     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6798       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6799       if(capt)
6800       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6801     }
6802   }
6803   DrawPosition(TRUE, NULL);
6804 }
6805
6806 int
6807 Explode(Board board, int fromX, int fromY, int toX, int toY)
6808 {
6809     if(gameInfo.variant == VariantAtomic &&
6810        (board[toY][toX] != EmptySquare ||                     // capture?
6811         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6812                          board[fromY][fromX] == BlackPawn   )
6813       )) {
6814         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6815         return TRUE;
6816     }
6817     return FALSE;
6818 }
6819
6820 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6821
6822 int CanPromote(ChessSquare piece, int y)
6823 {
6824         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6825         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6826         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6827            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6828            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6829                                                   gameInfo.variant == VariantMakruk) return FALSE;
6830         return (piece == BlackPawn && y == 1 ||
6831                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6832                 piece == BlackLance && y == 1 ||
6833                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6834 }
6835
6836 void LeftClick(ClickType clickType, int xPix, int yPix)
6837 {
6838     int x, y;
6839     Boolean saveAnimate;
6840     static int second = 0, promotionChoice = 0, clearFlag = 0;
6841     char promoChoice = NULLCHAR;
6842     ChessSquare piece;
6843
6844     if(appData.seekGraph && appData.icsActive && loggedOn &&
6845         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6846         SeekGraphClick(clickType, xPix, yPix, 0);
6847         return;
6848     }
6849
6850     if (clickType == Press) ErrorPopDown();
6851
6852     x = EventToSquare(xPix, BOARD_WIDTH);
6853     y = EventToSquare(yPix, BOARD_HEIGHT);
6854     if (!flipView && y >= 0) {
6855         y = BOARD_HEIGHT - 1 - y;
6856     }
6857     if (flipView && x >= 0) {
6858         x = BOARD_WIDTH - 1 - x;
6859     }
6860
6861     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6862         defaultPromoChoice = promoSweep;
6863         promoSweep = EmptySquare;   // terminate sweep
6864         promoDefaultAltered = TRUE;
6865         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6866     }
6867
6868     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6869         if(clickType == Release) return; // ignore upclick of click-click destination
6870         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6871         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6872         if(gameInfo.holdingsWidth &&
6873                 (WhiteOnMove(currentMove)
6874                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6875                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6876             // click in right holdings, for determining promotion piece
6877             ChessSquare p = boards[currentMove][y][x];
6878             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6879             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6880             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6881                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6882                 fromX = fromY = -1;
6883                 return;
6884             }
6885         }
6886         DrawPosition(FALSE, boards[currentMove]);
6887         return;
6888     }
6889
6890     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6891     if(clickType == Press
6892             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6893               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6894               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6895         return;
6896
6897     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6898         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6899
6900     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6901         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6902                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6903         defaultPromoChoice = DefaultPromoChoice(side);
6904     }
6905
6906     autoQueen = appData.alwaysPromoteToQueen;
6907
6908     if (fromX == -1) {
6909       int originalY = y;
6910       gatingPiece = EmptySquare;
6911       if (clickType != Press) {
6912         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6913             DragPieceEnd(xPix, yPix); dragging = 0;
6914             DrawPosition(FALSE, NULL);
6915         }
6916         return;
6917       }
6918       fromX = x; fromY = y; toX = toY = -1;
6919       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6920          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6921          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6922             /* First square */
6923             if (OKToStartUserMove(fromX, fromY)) {
6924                 second = 0;
6925                 MarkTargetSquares(0);
6926                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6927                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6928                     promoSweep = defaultPromoChoice;
6929                     selectFlag = 0; lastX = xPix; lastY = yPix;
6930                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6931                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6932                 }
6933                 if (appData.highlightDragging) {
6934                     SetHighlights(fromX, fromY, -1, -1);
6935                 }
6936             } else fromX = fromY = -1;
6937             return;
6938         }
6939     }
6940
6941     /* fromX != -1 */
6942     if (clickType == Press && gameMode != EditPosition) {
6943         ChessSquare fromP;
6944         ChessSquare toP;
6945         int frc;
6946
6947         // ignore off-board to clicks
6948         if(y < 0 || x < 0) return;
6949
6950         /* Check if clicking again on the same color piece */
6951         fromP = boards[currentMove][fromY][fromX];
6952         toP = boards[currentMove][y][x];
6953         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6954         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6955              WhitePawn <= toP && toP <= WhiteKing &&
6956              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6957              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6958             (BlackPawn <= fromP && fromP <= BlackKing &&
6959              BlackPawn <= toP && toP <= BlackKing &&
6960              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6961              !(fromP == BlackKing && toP == BlackRook && frc))) {
6962             /* Clicked again on same color piece -- changed his mind */
6963             second = (x == fromX && y == fromY);
6964             promoDefaultAltered = FALSE;
6965             MarkTargetSquares(1);
6966            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6967             if (appData.highlightDragging) {
6968                 SetHighlights(x, y, -1, -1);
6969             } else {
6970                 ClearHighlights();
6971             }
6972             if (OKToStartUserMove(x, y)) {
6973                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6974                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6975                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6976                  gatingPiece = boards[currentMove][fromY][fromX];
6977                 else gatingPiece = EmptySquare;
6978                 fromX = x;
6979                 fromY = y; dragging = 1;
6980                 MarkTargetSquares(0);
6981                 DragPieceBegin(xPix, yPix, FALSE);
6982                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6983                     promoSweep = defaultPromoChoice;
6984                     selectFlag = 0; lastX = xPix; lastY = yPix;
6985                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6986                 }
6987             }
6988            }
6989            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6990            second = FALSE; 
6991         }
6992         // ignore clicks on holdings
6993         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6994     }
6995
6996     if (clickType == Release && x == fromX && y == fromY) {
6997         DragPieceEnd(xPix, yPix); dragging = 0;
6998         if(clearFlag) {
6999             // a deferred attempt to click-click move an empty square on top of a piece
7000             boards[currentMove][y][x] = EmptySquare;
7001             ClearHighlights();
7002             DrawPosition(FALSE, boards[currentMove]);
7003             fromX = fromY = -1; clearFlag = 0;
7004             return;
7005         }
7006         if (appData.animateDragging) {
7007             /* Undo animation damage if any */
7008             DrawPosition(FALSE, NULL);
7009         }
7010         if (second) {
7011             /* Second up/down in same square; just abort move */
7012             second = 0;
7013             fromX = fromY = -1;
7014             gatingPiece = EmptySquare;
7015             ClearHighlights();
7016             gotPremove = 0;
7017             ClearPremoveHighlights();
7018         } else {
7019             /* First upclick in same square; start click-click mode */
7020             SetHighlights(x, y, -1, -1);
7021         }
7022         return;
7023     }
7024
7025     clearFlag = 0;
7026
7027     /* we now have a different from- and (possibly off-board) to-square */
7028     /* Completed move */
7029     toX = x;
7030     toY = y;
7031     saveAnimate = appData.animate;
7032     MarkTargetSquares(1);
7033     if (clickType == Press) {
7034         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7035             // must be Edit Position mode with empty-square selected
7036             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7037             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7038             return;
7039         }
7040         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7041             ChessSquare piece = boards[currentMove][fromY][fromX];
7042             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7043             promoSweep = defaultPromoChoice;
7044             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7045             selectFlag = 0; lastX = xPix; lastY = yPix;
7046             Sweep(0); // Pawn that is going to promote: preview promotion piece
7047             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7048             DrawPosition(FALSE, boards[currentMove]);
7049             return;
7050         }
7051         /* Finish clickclick move */
7052         if (appData.animate || appData.highlightLastMove) {
7053             SetHighlights(fromX, fromY, toX, toY);
7054         } else {
7055             ClearHighlights();
7056         }
7057     } else {
7058         /* Finish drag move */
7059         if (appData.highlightLastMove) {
7060             SetHighlights(fromX, fromY, toX, toY);
7061         } else {
7062             ClearHighlights();
7063         }
7064         DragPieceEnd(xPix, yPix); dragging = 0;
7065         /* Don't animate move and drag both */
7066         appData.animate = FALSE;
7067     }
7068
7069     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7070     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7071         ChessSquare piece = boards[currentMove][fromY][fromX];
7072         if(gameMode == EditPosition && piece != EmptySquare &&
7073            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7074             int n;
7075
7076             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7077                 n = PieceToNumber(piece - (int)BlackPawn);
7078                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7079                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7080                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7081             } else
7082             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7083                 n = PieceToNumber(piece);
7084                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7085                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7086                 boards[currentMove][n][BOARD_WIDTH-2]++;
7087             }
7088             boards[currentMove][fromY][fromX] = EmptySquare;
7089         }
7090         ClearHighlights();
7091         fromX = fromY = -1;
7092         DrawPosition(TRUE, boards[currentMove]);
7093         return;
7094     }
7095
7096     // off-board moves should not be highlighted
7097     if(x < 0 || y < 0) ClearHighlights();
7098
7099     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7100
7101     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7102         SetHighlights(fromX, fromY, toX, toY);
7103         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7104             // [HGM] super: promotion to captured piece selected from holdings
7105             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7106             promotionChoice = TRUE;
7107             // kludge follows to temporarily execute move on display, without promoting yet
7108             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7109             boards[currentMove][toY][toX] = p;
7110             DrawPosition(FALSE, boards[currentMove]);
7111             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7112             boards[currentMove][toY][toX] = q;
7113             DisplayMessage("Click in holdings to choose piece", "");
7114             return;
7115         }
7116         PromotionPopUp();
7117     } else {
7118         int oldMove = currentMove;
7119         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7120         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7121         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7122         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7123            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7124             DrawPosition(TRUE, boards[currentMove]);
7125         fromX = fromY = -1;
7126     }
7127     appData.animate = saveAnimate;
7128     if (appData.animate || appData.animateDragging) {
7129         /* Undo animation damage if needed */
7130         DrawPosition(FALSE, NULL);
7131     }
7132 }
7133
7134 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7135 {   // front-end-free part taken out of PieceMenuPopup
7136     int whichMenu; int xSqr, ySqr;
7137
7138     if(seekGraphUp) { // [HGM] seekgraph
7139         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7140         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7141         return -2;
7142     }
7143
7144     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7145          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7146         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7147         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7148         if(action == Press)   {
7149             originalFlip = flipView;
7150             flipView = !flipView; // temporarily flip board to see game from partners perspective
7151             DrawPosition(TRUE, partnerBoard);
7152             DisplayMessage(partnerStatus, "");
7153             partnerUp = TRUE;
7154         } else if(action == Release) {
7155             flipView = originalFlip;
7156             DrawPosition(TRUE, boards[currentMove]);
7157             partnerUp = FALSE;
7158         }
7159         return -2;
7160     }
7161
7162     xSqr = EventToSquare(x, BOARD_WIDTH);
7163     ySqr = EventToSquare(y, BOARD_HEIGHT);
7164     if (action == Release) {
7165         if(pieceSweep != EmptySquare) {
7166             EditPositionMenuEvent(pieceSweep, toX, toY);
7167             pieceSweep = EmptySquare;
7168         } else UnLoadPV(); // [HGM] pv
7169     }
7170     if (action != Press) return -2; // return code to be ignored
7171     switch (gameMode) {
7172       case IcsExamining:
7173         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7174       case EditPosition:
7175         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7176         if (xSqr < 0 || ySqr < 0) return -1;
7177         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7178         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7179         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7180         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7181         NextPiece(0);
7182         return 2; // grab
7183       case IcsObserving:
7184         if(!appData.icsEngineAnalyze) return -1;
7185       case IcsPlayingWhite:
7186       case IcsPlayingBlack:
7187         if(!appData.zippyPlay) goto noZip;
7188       case AnalyzeMode:
7189       case AnalyzeFile:
7190       case MachinePlaysWhite:
7191       case MachinePlaysBlack:
7192       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7193         if (!appData.dropMenu) {
7194           LoadPV(x, y);
7195           return 2; // flag front-end to grab mouse events
7196         }
7197         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7198            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7199       case EditGame:
7200       noZip:
7201         if (xSqr < 0 || ySqr < 0) return -1;
7202         if (!appData.dropMenu || appData.testLegality &&
7203             gameInfo.variant != VariantBughouse &&
7204             gameInfo.variant != VariantCrazyhouse) return -1;
7205         whichMenu = 1; // drop menu
7206         break;
7207       default:
7208         return -1;
7209     }
7210
7211     if (((*fromX = xSqr) < 0) ||
7212         ((*fromY = ySqr) < 0)) {
7213         *fromX = *fromY = -1;
7214         return -1;
7215     }
7216     if (flipView)
7217       *fromX = BOARD_WIDTH - 1 - *fromX;
7218     else
7219       *fromY = BOARD_HEIGHT - 1 - *fromY;
7220
7221     return whichMenu;
7222 }
7223
7224 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7225 {
7226 //    char * hint = lastHint;
7227     FrontEndProgramStats stats;
7228
7229     stats.which = cps == &first ? 0 : 1;
7230     stats.depth = cpstats->depth;
7231     stats.nodes = cpstats->nodes;
7232     stats.score = cpstats->score;
7233     stats.time = cpstats->time;
7234     stats.pv = cpstats->movelist;
7235     stats.hint = lastHint;
7236     stats.an_move_index = 0;
7237     stats.an_move_count = 0;
7238
7239     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7240         stats.hint = cpstats->move_name;
7241         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7242         stats.an_move_count = cpstats->nr_moves;
7243     }
7244
7245     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7246
7247     SetProgramStats( &stats );
7248 }
7249
7250 void
7251 ClearEngineOutputPane(int which)
7252 {
7253     static FrontEndProgramStats dummyStats;
7254     dummyStats.which = which;
7255     dummyStats.pv = "#";
7256     SetProgramStats( &dummyStats );
7257 }
7258
7259 #define MAXPLAYERS 500
7260
7261 char *
7262 TourneyStandings(int display)
7263 {
7264     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7265     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7266     char result, *p, *names[MAXPLAYERS];
7267
7268     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7269         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7270     names[0] = p = strdup(appData.participants);
7271     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7272
7273     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7274
7275     while(result = appData.results[nr]) {
7276         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7277         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7278         wScore = bScore = 0;
7279         switch(result) {
7280           case '+': wScore = 2; break;
7281           case '-': bScore = 2; break;
7282           case '=': wScore = bScore = 1; break;
7283           case ' ':
7284           case '*': return strdup("busy"); // tourney not finished
7285         }
7286         score[w] += wScore;
7287         score[b] += bScore;
7288         games[w]++;
7289         games[b]++;
7290         nr++;
7291     }
7292     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7293     for(w=0; w<nPlayers; w++) {
7294         bScore = -1;
7295         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7296         ranking[w] = b; points[w] = bScore; score[b] = -2;
7297     }
7298     p = malloc(nPlayers*34+1);
7299     for(w=0; w<nPlayers && w<display; w++)
7300         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7301     free(names[0]);
7302     return p;
7303 }
7304
7305 void
7306 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7307 {       // count all piece types
7308         int p, f, r;
7309         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7310         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7311         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7312                 p = board[r][f];
7313                 pCnt[p]++;
7314                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7315                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7316                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7317                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7318                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7319                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7320         }
7321 }
7322
7323 int
7324 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7325 {
7326         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7327         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7328
7329         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7330         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7331         if(myPawns == 2 && nMine == 3) // KPP
7332             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7333         if(myPawns == 1 && nMine == 2) // KP
7334             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7335         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7336             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7337         if(myPawns) return FALSE;
7338         if(pCnt[WhiteRook+side])
7339             return pCnt[BlackRook-side] ||
7340                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7341                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7342                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7343         if(pCnt[WhiteCannon+side]) {
7344             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7345             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7346         }
7347         if(pCnt[WhiteKnight+side])
7348             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7349         return FALSE;
7350 }
7351
7352 int
7353 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7354 {
7355         VariantClass v = gameInfo.variant;
7356
7357         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7358         if(v == VariantShatranj) return TRUE; // always winnable through baring
7359         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7360         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7361
7362         if(v == VariantXiangqi) {
7363                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7364
7365                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7366                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7367                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7368                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7369                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7370                 if(stale) // we have at least one last-rank P plus perhaps C
7371                     return majors // KPKX
7372                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7373                 else // KCA*E*
7374                     return pCnt[WhiteFerz+side] // KCAK
7375                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7376                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7377                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7378
7379         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7380                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7381
7382                 if(nMine == 1) return FALSE; // bare King
7383                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7384                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7385                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7386                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7387                 if(pCnt[WhiteKnight+side])
7388                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7389                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7390                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7391                 if(nBishops)
7392                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7393                 if(pCnt[WhiteAlfil+side])
7394                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7395                 if(pCnt[WhiteWazir+side])
7396                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7397         }
7398
7399         return TRUE;
7400 }
7401
7402 int
7403 CompareWithRights(Board b1, Board b2)
7404 {
7405     int rights = 0;
7406     if(!CompareBoards(b1, b2)) return FALSE;
7407     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7408     /* compare castling rights */
7409     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7410            rights++; /* King lost rights, while rook still had them */
7411     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7412         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7413            rights++; /* but at least one rook lost them */
7414     }
7415     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7416            rights++;
7417     if( b1[CASTLING][5] != NoRights ) {
7418         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7419            rights++;
7420     }
7421     return rights == 0;
7422 }
7423
7424 int
7425 Adjudicate(ChessProgramState *cps)
7426 {       // [HGM] some adjudications useful with buggy engines
7427         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7428         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7429         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7430         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7431         int k, count = 0; static int bare = 1;
7432         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7433         Boolean canAdjudicate = !appData.icsActive;
7434
7435         // most tests only when we understand the game, i.e. legality-checking on
7436             if( appData.testLegality )
7437             {   /* [HGM] Some more adjudications for obstinate engines */
7438                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7439                 static int moveCount = 6;
7440                 ChessMove result;
7441                 char *reason = NULL;
7442
7443                 /* Count what is on board. */
7444                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7445
7446                 /* Some material-based adjudications that have to be made before stalemate test */
7447                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7448                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7449                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7450                      if(canAdjudicate && appData.checkMates) {
7451                          if(engineOpponent)
7452                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7453                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7454                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7455                          return 1;
7456                      }
7457                 }
7458
7459                 /* Bare King in Shatranj (loses) or Losers (wins) */
7460                 if( nrW == 1 || nrB == 1) {
7461                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7462                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7463                      if(canAdjudicate && appData.checkMates) {
7464                          if(engineOpponent)
7465                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7466                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7467                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7468                          return 1;
7469                      }
7470                   } else
7471                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7472                   {    /* bare King */
7473                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7474                         if(canAdjudicate && appData.checkMates) {
7475                             /* but only adjudicate if adjudication enabled */
7476                             if(engineOpponent)
7477                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7478                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7479                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7480                             return 1;
7481                         }
7482                   }
7483                 } else bare = 1;
7484
7485
7486             // don't wait for engine to announce game end if we can judge ourselves
7487             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7488               case MT_CHECK:
7489                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7490                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7491                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7492                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7493                             checkCnt++;
7494                         if(checkCnt >= 2) {
7495                             reason = "Xboard adjudication: 3rd check";
7496                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7497                             break;
7498                         }
7499                     }
7500                 }
7501               case MT_NONE:
7502               default:
7503                 break;
7504               case MT_STALEMATE:
7505               case MT_STAINMATE:
7506                 reason = "Xboard adjudication: Stalemate";
7507                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7508                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7509                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7510                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7511                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7512                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7513                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7514                                                                         EP_CHECKMATE : EP_WINS);
7515                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7516                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7517                 }
7518                 break;
7519               case MT_CHECKMATE:
7520                 reason = "Xboard adjudication: Checkmate";
7521                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7522                 break;
7523             }
7524
7525                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7526                     case EP_STALEMATE:
7527                         result = GameIsDrawn; break;
7528                     case EP_CHECKMATE:
7529                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7530                     case EP_WINS:
7531                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7532                     default:
7533                         result = EndOfFile;
7534                 }
7535                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7536                     if(engineOpponent)
7537                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7538                     GameEnds( result, reason, GE_XBOARD );
7539                     return 1;
7540                 }
7541
7542                 /* Next absolutely insufficient mating material. */
7543                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7544                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7545                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7546
7547                      /* always flag draws, for judging claims */
7548                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7549
7550                      if(canAdjudicate && appData.materialDraws) {
7551                          /* but only adjudicate them if adjudication enabled */
7552                          if(engineOpponent) {
7553                            SendToProgram("force\n", engineOpponent); // suppress reply
7554                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7555                          }
7556                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7557                          return 1;
7558                      }
7559                 }
7560
7561                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7562                 if(gameInfo.variant == VariantXiangqi ?
7563                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7564                  : nrW + nrB == 4 &&
7565                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7566                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7567                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7568                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7569                    ) ) {
7570                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7571                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7572                           if(engineOpponent) {
7573                             SendToProgram("force\n", engineOpponent); // suppress reply
7574                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7575                           }
7576                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7577                           return 1;
7578                      }
7579                 } else moveCount = 6;
7580             }
7581         if (appData.debugMode) { int i;
7582             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7583                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7584                     appData.drawRepeats);
7585             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7586               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7587
7588         }
7589
7590         // Repetition draws and 50-move rule can be applied independently of legality testing
7591
7592                 /* Check for rep-draws */
7593                 count = 0;
7594                 for(k = forwardMostMove-2;
7595                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7596                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7597                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7598                     k-=2)
7599                 {   int rights=0;
7600                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7601                         /* compare castling rights */
7602                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7603                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7604                                 rights++; /* King lost rights, while rook still had them */
7605                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7606                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7607                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7608                                    rights++; /* but at least one rook lost them */
7609                         }
7610                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7611                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7612                                 rights++;
7613                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7614                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7615                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7616                                    rights++;
7617                         }
7618                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7619                             && appData.drawRepeats > 1) {
7620                              /* adjudicate after user-specified nr of repeats */
7621                              int result = GameIsDrawn;
7622                              char *details = "XBoard adjudication: repetition draw";
7623                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7624                                 // [HGM] xiangqi: check for forbidden perpetuals
7625                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7626                                 for(m=forwardMostMove; m>k; m-=2) {
7627                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7628                                         ourPerpetual = 0; // the current mover did not always check
7629                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7630                                         hisPerpetual = 0; // the opponent did not always check
7631                                 }
7632                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7633                                                                         ourPerpetual, hisPerpetual);
7634                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7635                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7636                                     details = "Xboard adjudication: perpetual checking";
7637                                 } else
7638                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7639                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7640                                 } else
7641                                 // Now check for perpetual chases
7642                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7643                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7644                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7645                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7646                                         static char resdet[MSG_SIZ];
7647                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7648                                         details = resdet;
7649                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7650                                     } else
7651                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7652                                         break; // Abort repetition-checking loop.
7653                                 }
7654                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7655                              }
7656                              if(engineOpponent) {
7657                                SendToProgram("force\n", engineOpponent); // suppress reply
7658                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7659                              }
7660                              GameEnds( result, details, GE_XBOARD );
7661                              return 1;
7662                         }
7663                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7664                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7665                     }
7666                 }
7667
7668                 /* Now we test for 50-move draws. Determine ply count */
7669                 count = forwardMostMove;
7670                 /* look for last irreversble move */
7671                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7672                     count--;
7673                 /* if we hit starting position, add initial plies */
7674                 if( count == backwardMostMove )
7675                     count -= initialRulePlies;
7676                 count = forwardMostMove - count;
7677                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7678                         // adjust reversible move counter for checks in Xiangqi
7679                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7680                         if(i < backwardMostMove) i = backwardMostMove;
7681                         while(i <= forwardMostMove) {
7682                                 lastCheck = inCheck; // check evasion does not count
7683                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7684                                 if(inCheck || lastCheck) count--; // check does not count
7685                                 i++;
7686                         }
7687                 }
7688                 if( count >= 100)
7689                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7690                          /* this is used to judge if draw claims are legal */
7691                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7692                          if(engineOpponent) {
7693                            SendToProgram("force\n", engineOpponent); // suppress reply
7694                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7695                          }
7696                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7697                          return 1;
7698                 }
7699
7700                 /* if draw offer is pending, treat it as a draw claim
7701                  * when draw condition present, to allow engines a way to
7702                  * claim draws before making their move to avoid a race
7703                  * condition occurring after their move
7704                  */
7705                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7706                          char *p = NULL;
7707                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7708                              p = "Draw claim: 50-move rule";
7709                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7710                              p = "Draw claim: 3-fold repetition";
7711                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7712                              p = "Draw claim: insufficient mating material";
7713                          if( p != NULL && canAdjudicate) {
7714                              if(engineOpponent) {
7715                                SendToProgram("force\n", engineOpponent); // suppress reply
7716                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7717                              }
7718                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7719                              return 1;
7720                          }
7721                 }
7722
7723                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7724                     if(engineOpponent) {
7725                       SendToProgram("force\n", engineOpponent); // suppress reply
7726                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7727                     }
7728                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7729                     return 1;
7730                 }
7731         return 0;
7732 }
7733
7734 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7735 {   // [HGM] book: this routine intercepts moves to simulate book replies
7736     char *bookHit = NULL;
7737
7738     //first determine if the incoming move brings opponent into his book
7739     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7740         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7741     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7742     if(bookHit != NULL && !cps->bookSuspend) {
7743         // make sure opponent is not going to reply after receiving move to book position
7744         SendToProgram("force\n", cps);
7745         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7746     }
7747     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7748     // now arrange restart after book miss
7749     if(bookHit) {
7750         // after a book hit we never send 'go', and the code after the call to this routine
7751         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7752         char buf[MSG_SIZ], *move = bookHit;
7753         if(cps->useSAN) {
7754             int fromX, fromY, toX, toY;
7755             char promoChar;
7756             ChessMove moveType;
7757             move = buf + 30;
7758             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7759                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7760                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7761                                     PosFlags(forwardMostMove),
7762                                     fromY, fromX, toY, toX, promoChar, move);
7763             } else {
7764                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7765                 bookHit = NULL;
7766             }
7767         }
7768         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7769         SendToProgram(buf, cps);
7770         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7771     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7772         SendToProgram("go\n", cps);
7773         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7774     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7775         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7776             SendToProgram("go\n", cps);
7777         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7778     }
7779     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7780 }
7781
7782 char *savedMessage;
7783 ChessProgramState *savedState;
7784 void DeferredBookMove(void)
7785 {
7786         if(savedState->lastPing != savedState->lastPong)
7787                     ScheduleDelayedEvent(DeferredBookMove, 10);
7788         else
7789         HandleMachineMove(savedMessage, savedState);
7790 }
7791
7792 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7793
7794 void
7795 HandleMachineMove(message, cps)
7796      char *message;
7797      ChessProgramState *cps;
7798 {
7799     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7800     char realname[MSG_SIZ];
7801     int fromX, fromY, toX, toY;
7802     ChessMove moveType;
7803     char promoChar;
7804     char *p, *pv=buf1;
7805     int machineWhite;
7806     char *bookHit;
7807
7808     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7809         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7810         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7811             DisplayError(_("Invalid pairing from pairing engine"), 0);
7812             return;
7813         }
7814         pairingReceived = 1;
7815         NextMatchGame();
7816         return; // Skim the pairing messages here.
7817     }
7818
7819     cps->userError = 0;
7820
7821 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7822     /*
7823      * Kludge to ignore BEL characters
7824      */
7825     while (*message == '\007') message++;
7826
7827     /*
7828      * [HGM] engine debug message: ignore lines starting with '#' character
7829      */
7830     if(cps->debug && *message == '#') return;
7831
7832     /*
7833      * Look for book output
7834      */
7835     if (cps == &first && bookRequested) {
7836         if (message[0] == '\t' || message[0] == ' ') {
7837             /* Part of the book output is here; append it */
7838             strcat(bookOutput, message);
7839             strcat(bookOutput, "  \n");
7840             return;
7841         } else if (bookOutput[0] != NULLCHAR) {
7842             /* All of book output has arrived; display it */
7843             char *p = bookOutput;
7844             while (*p != NULLCHAR) {
7845                 if (*p == '\t') *p = ' ';
7846                 p++;
7847             }
7848             DisplayInformation(bookOutput);
7849             bookRequested = FALSE;
7850             /* Fall through to parse the current output */
7851         }
7852     }
7853
7854     /*
7855      * Look for machine move.
7856      */
7857     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7858         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7859     {
7860         /* This method is only useful on engines that support ping */
7861         if (cps->lastPing != cps->lastPong) {
7862           if (gameMode == BeginningOfGame) {
7863             /* Extra move from before last new; ignore */
7864             if (appData.debugMode) {
7865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7866             }
7867           } else {
7868             if (appData.debugMode) {
7869                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7870                         cps->which, gameMode);
7871             }
7872
7873             SendToProgram("undo\n", cps);
7874           }
7875           return;
7876         }
7877
7878         switch (gameMode) {
7879           case BeginningOfGame:
7880             /* Extra move from before last reset; ignore */
7881             if (appData.debugMode) {
7882                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7883             }
7884             return;
7885
7886           case EndOfGame:
7887           case IcsIdle:
7888           default:
7889             /* Extra move after we tried to stop.  The mode test is
7890                not a reliable way of detecting this problem, but it's
7891                the best we can do on engines that don't support ping.
7892             */
7893             if (appData.debugMode) {
7894                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7895                         cps->which, gameMode);
7896             }
7897             SendToProgram("undo\n", cps);
7898             return;
7899
7900           case MachinePlaysWhite:
7901           case IcsPlayingWhite:
7902             machineWhite = TRUE;
7903             break;
7904
7905           case MachinePlaysBlack:
7906           case IcsPlayingBlack:
7907             machineWhite = FALSE;
7908             break;
7909
7910           case TwoMachinesPlay:
7911             machineWhite = (cps->twoMachinesColor[0] == 'w');
7912             break;
7913         }
7914         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7915             if (appData.debugMode) {
7916                 fprintf(debugFP,
7917                         "Ignoring move out of turn by %s, gameMode %d"
7918                         ", forwardMost %d\n",
7919                         cps->which, gameMode, forwardMostMove);
7920             }
7921             return;
7922         }
7923
7924     if (appData.debugMode) { int f = forwardMostMove;
7925         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7926                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7927                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7928     }
7929         if(cps->alphaRank) AlphaRank(machineMove, 4);
7930         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7931                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7932             /* Machine move could not be parsed; ignore it. */
7933           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7934                     machineMove, _(cps->which));
7935             DisplayError(buf1, 0);
7936             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7937                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7938             if (gameMode == TwoMachinesPlay) {
7939               GameEnds(machineWhite ? BlackWins : WhiteWins,
7940                        buf1, GE_XBOARD);
7941             }
7942             return;
7943         }
7944
7945         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7946         /* So we have to redo legality test with true e.p. status here,  */
7947         /* to make sure an illegal e.p. capture does not slip through,   */
7948         /* to cause a forfeit on a justified illegal-move complaint      */
7949         /* of the opponent.                                              */
7950         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7951            ChessMove moveType;
7952            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7953                              fromY, fromX, toY, toX, promoChar);
7954             if (appData.debugMode) {
7955                 int i;
7956                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7957                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7958                 fprintf(debugFP, "castling rights\n");
7959             }
7960             if(moveType == IllegalMove) {
7961               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7962                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7963                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7964                            buf1, GE_XBOARD);
7965                 return;
7966            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7967            /* [HGM] Kludge to handle engines that send FRC-style castling
7968               when they shouldn't (like TSCP-Gothic) */
7969            switch(moveType) {
7970              case WhiteASideCastleFR:
7971              case BlackASideCastleFR:
7972                toX+=2;
7973                currentMoveString[2]++;
7974                break;
7975              case WhiteHSideCastleFR:
7976              case BlackHSideCastleFR:
7977                toX--;
7978                currentMoveString[2]--;
7979                break;
7980              default: ; // nothing to do, but suppresses warning of pedantic compilers
7981            }
7982         }
7983         hintRequested = FALSE;
7984         lastHint[0] = NULLCHAR;
7985         bookRequested = FALSE;
7986         /* Program may be pondering now */
7987         cps->maybeThinking = TRUE;
7988         if (cps->sendTime == 2) cps->sendTime = 1;
7989         if (cps->offeredDraw) cps->offeredDraw--;
7990
7991         /* [AS] Save move info*/
7992         pvInfoList[ forwardMostMove ].score = programStats.score;
7993         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7994         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7995
7996         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7997
7998         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7999         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8000             int count = 0;
8001
8002             while( count < adjudicateLossPlies ) {
8003                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8004
8005                 if( count & 1 ) {
8006                     score = -score; /* Flip score for winning side */
8007                 }
8008
8009                 if( score > adjudicateLossThreshold ) {
8010                     break;
8011                 }
8012
8013                 count++;
8014             }
8015
8016             if( count >= adjudicateLossPlies ) {
8017                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8018
8019                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8020                     "Xboard adjudication",
8021                     GE_XBOARD );
8022
8023                 return;
8024             }
8025         }
8026
8027         if(Adjudicate(cps)) {
8028             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8029             return; // [HGM] adjudicate: for all automatic game ends
8030         }
8031
8032 #if ZIPPY
8033         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8034             first.initDone) {
8035           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8036                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8037                 SendToICS("draw ");
8038                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8039           }
8040           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8041           ics_user_moved = 1;
8042           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8043                 char buf[3*MSG_SIZ];
8044
8045                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8046                         programStats.score / 100.,
8047                         programStats.depth,
8048                         programStats.time / 100.,
8049                         (unsigned int)programStats.nodes,
8050                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8051                         programStats.movelist);
8052                 SendToICS(buf);
8053 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8054           }
8055         }
8056 #endif
8057
8058         /* [AS] Clear stats for next move */
8059         ClearProgramStats();
8060         thinkOutput[0] = NULLCHAR;
8061         hiddenThinkOutputState = 0;
8062
8063         bookHit = NULL;
8064         if (gameMode == TwoMachinesPlay) {
8065             /* [HGM] relaying draw offers moved to after reception of move */
8066             /* and interpreting offer as claim if it brings draw condition */
8067             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8068                 SendToProgram("draw\n", cps->other);
8069             }
8070             if (cps->other->sendTime) {
8071                 SendTimeRemaining(cps->other,
8072                                   cps->other->twoMachinesColor[0] == 'w');
8073             }
8074             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8075             if (firstMove && !bookHit) {
8076                 firstMove = FALSE;
8077                 if (cps->other->useColors) {
8078                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8079                 }
8080                 SendToProgram("go\n", cps->other);
8081             }
8082             cps->other->maybeThinking = TRUE;
8083         }
8084
8085         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8086
8087         if (!pausing && appData.ringBellAfterMoves) {
8088             RingBell();
8089         }
8090
8091         /*
8092          * Reenable menu items that were disabled while
8093          * machine was thinking
8094          */
8095         if (gameMode != TwoMachinesPlay)
8096             SetUserThinkingEnables();
8097
8098         // [HGM] book: after book hit opponent has received move and is now in force mode
8099         // force the book reply into it, and then fake that it outputted this move by jumping
8100         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8101         if(bookHit) {
8102                 static char bookMove[MSG_SIZ]; // a bit generous?
8103
8104                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8105                 strcat(bookMove, bookHit);
8106                 message = bookMove;
8107                 cps = cps->other;
8108                 programStats.nodes = programStats.depth = programStats.time =
8109                 programStats.score = programStats.got_only_move = 0;
8110                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8111
8112                 if(cps->lastPing != cps->lastPong) {
8113                     savedMessage = message; // args for deferred call
8114                     savedState = cps;
8115                     ScheduleDelayedEvent(DeferredBookMove, 10);
8116                     return;
8117                 }
8118                 goto FakeBookMove;
8119         }
8120
8121         return;
8122     }
8123
8124     /* Set special modes for chess engines.  Later something general
8125      *  could be added here; for now there is just one kludge feature,
8126      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8127      *  when "xboard" is given as an interactive command.
8128      */
8129     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8130         cps->useSigint = FALSE;
8131         cps->useSigterm = FALSE;
8132     }
8133     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8134       ParseFeatures(message+8, cps);
8135       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8136     }
8137
8138     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8139                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8140       int dummy, s=6; char buf[MSG_SIZ];
8141       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8142       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8143       if(startedFromSetupPosition) return;
8144       ParseFEN(boards[0], &dummy, message+s);
8145       DrawPosition(TRUE, boards[0]);
8146       startedFromSetupPosition = TRUE;
8147       return;
8148     }
8149     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8150      * want this, I was asked to put it in, and obliged.
8151      */
8152     if (!strncmp(message, "setboard ", 9)) {
8153         Board initial_position;
8154
8155         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8156
8157         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8158             DisplayError(_("Bad FEN received from engine"), 0);
8159             return ;
8160         } else {
8161            Reset(TRUE, FALSE);
8162            CopyBoard(boards[0], initial_position);
8163            initialRulePlies = FENrulePlies;
8164            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8165            else gameMode = MachinePlaysBlack;
8166            DrawPosition(FALSE, boards[currentMove]);
8167         }
8168         return;
8169     }
8170
8171     /*
8172      * Look for communication commands
8173      */
8174     if (!strncmp(message, "telluser ", 9)) {
8175         if(message[9] == '\\' && message[10] == '\\')
8176             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8177         PlayTellSound();
8178         DisplayNote(message + 9);
8179         return;
8180     }
8181     if (!strncmp(message, "tellusererror ", 14)) {
8182         cps->userError = 1;
8183         if(message[14] == '\\' && message[15] == '\\')
8184             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8185         PlayTellSound();
8186         DisplayError(message + 14, 0);
8187         return;
8188     }
8189     if (!strncmp(message, "tellopponent ", 13)) {
8190       if (appData.icsActive) {
8191         if (loggedOn) {
8192           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8193           SendToICS(buf1);
8194         }
8195       } else {
8196         DisplayNote(message + 13);
8197       }
8198       return;
8199     }
8200     if (!strncmp(message, "tellothers ", 11)) {
8201       if (appData.icsActive) {
8202         if (loggedOn) {
8203           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8204           SendToICS(buf1);
8205         }
8206       }
8207       return;
8208     }
8209     if (!strncmp(message, "tellall ", 8)) {
8210       if (appData.icsActive) {
8211         if (loggedOn) {
8212           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8213           SendToICS(buf1);
8214         }
8215       } else {
8216         DisplayNote(message + 8);
8217       }
8218       return;
8219     }
8220     if (strncmp(message, "warning", 7) == 0) {
8221         /* Undocumented feature, use tellusererror in new code */
8222         DisplayError(message, 0);
8223         return;
8224     }
8225     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8226         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8227         strcat(realname, " query");
8228         AskQuestion(realname, buf2, buf1, cps->pr);
8229         return;
8230     }
8231     /* Commands from the engine directly to ICS.  We don't allow these to be
8232      *  sent until we are logged on. Crafty kibitzes have been known to
8233      *  interfere with the login process.
8234      */
8235     if (loggedOn) {
8236         if (!strncmp(message, "tellics ", 8)) {
8237             SendToICS(message + 8);
8238             SendToICS("\n");
8239             return;
8240         }
8241         if (!strncmp(message, "tellicsnoalias ", 15)) {
8242             SendToICS(ics_prefix);
8243             SendToICS(message + 15);
8244             SendToICS("\n");
8245             return;
8246         }
8247         /* The following are for backward compatibility only */
8248         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8249             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8250             SendToICS(ics_prefix);
8251             SendToICS(message);
8252             SendToICS("\n");
8253             return;
8254         }
8255     }
8256     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8257         return;
8258     }
8259     /*
8260      * If the move is illegal, cancel it and redraw the board.
8261      * Also deal with other error cases.  Matching is rather loose
8262      * here to accommodate engines written before the spec.
8263      */
8264     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8265         strncmp(message, "Error", 5) == 0) {
8266         if (StrStr(message, "name") ||
8267             StrStr(message, "rating") || StrStr(message, "?") ||
8268             StrStr(message, "result") || StrStr(message, "board") ||
8269             StrStr(message, "bk") || StrStr(message, "computer") ||
8270             StrStr(message, "variant") || StrStr(message, "hint") ||
8271             StrStr(message, "random") || StrStr(message, "depth") ||
8272             StrStr(message, "accepted")) {
8273             return;
8274         }
8275         if (StrStr(message, "protover")) {
8276           /* Program is responding to input, so it's apparently done
8277              initializing, and this error message indicates it is
8278              protocol version 1.  So we don't need to wait any longer
8279              for it to initialize and send feature commands. */
8280           FeatureDone(cps, 1);
8281           cps->protocolVersion = 1;
8282           return;
8283         }
8284         cps->maybeThinking = FALSE;
8285
8286         if (StrStr(message, "draw")) {
8287             /* Program doesn't have "draw" command */
8288             cps->sendDrawOffers = 0;
8289             return;
8290         }
8291         if (cps->sendTime != 1 &&
8292             (StrStr(message, "time") || StrStr(message, "otim"))) {
8293           /* Program apparently doesn't have "time" or "otim" command */
8294           cps->sendTime = 0;
8295           return;
8296         }
8297         if (StrStr(message, "analyze")) {
8298             cps->analysisSupport = FALSE;
8299             cps->analyzing = FALSE;
8300 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8301             EditGameEvent(); // [HGM] try to preserve loaded game
8302             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8303             DisplayError(buf2, 0);
8304             return;
8305         }
8306         if (StrStr(message, "(no matching move)st")) {
8307           /* Special kludge for GNU Chess 4 only */
8308           cps->stKludge = TRUE;
8309           SendTimeControl(cps, movesPerSession, timeControl,
8310                           timeIncrement, appData.searchDepth,
8311                           searchTime);
8312           return;
8313         }
8314         if (StrStr(message, "(no matching move)sd")) {
8315           /* Special kludge for GNU Chess 4 only */
8316           cps->sdKludge = TRUE;
8317           SendTimeControl(cps, movesPerSession, timeControl,
8318                           timeIncrement, appData.searchDepth,
8319                           searchTime);
8320           return;
8321         }
8322         if (!StrStr(message, "llegal")) {
8323             return;
8324         }
8325         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8326             gameMode == IcsIdle) return;
8327         if (forwardMostMove <= backwardMostMove) return;
8328         if (pausing) PauseEvent();
8329       if(appData.forceIllegal) {
8330             // [HGM] illegal: machine refused move; force position after move into it
8331           SendToProgram("force\n", cps);
8332           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8333                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8334                 // when black is to move, while there might be nothing on a2 or black
8335                 // might already have the move. So send the board as if white has the move.
8336                 // But first we must change the stm of the engine, as it refused the last move
8337                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8338                 if(WhiteOnMove(forwardMostMove)) {
8339                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8340                     SendBoard(cps, forwardMostMove); // kludgeless board
8341                 } else {
8342                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8343                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8344                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8345                 }
8346           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8347             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8348                  gameMode == TwoMachinesPlay)
8349               SendToProgram("go\n", cps);
8350             return;
8351       } else
8352         if (gameMode == PlayFromGameFile) {
8353             /* Stop reading this game file */
8354             gameMode = EditGame;
8355             ModeHighlight();
8356         }
8357         /* [HGM] illegal-move claim should forfeit game when Xboard */
8358         /* only passes fully legal moves                            */
8359         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8360             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8361                                 "False illegal-move claim", GE_XBOARD );
8362             return; // do not take back move we tested as valid
8363         }
8364         currentMove = forwardMostMove-1;
8365         DisplayMove(currentMove-1); /* before DisplayMoveError */
8366         SwitchClocks(forwardMostMove-1); // [HGM] race
8367         DisplayBothClocks();
8368         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8369                 parseList[currentMove], _(cps->which));
8370         DisplayMoveError(buf1);
8371         DrawPosition(FALSE, boards[currentMove]);
8372
8373         SetUserThinkingEnables();
8374         return;
8375     }
8376     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8377         /* Program has a broken "time" command that
8378            outputs a string not ending in newline.
8379            Don't use it. */
8380         cps->sendTime = 0;
8381     }
8382
8383     /*
8384      * If chess program startup fails, exit with an error message.
8385      * Attempts to recover here are futile.
8386      */
8387     if ((StrStr(message, "unknown host") != NULL)
8388         || (StrStr(message, "No remote directory") != NULL)
8389         || (StrStr(message, "not found") != NULL)
8390         || (StrStr(message, "No such file") != NULL)
8391         || (StrStr(message, "can't alloc") != NULL)
8392         || (StrStr(message, "Permission denied") != NULL)) {
8393
8394         cps->maybeThinking = FALSE;
8395         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8396                 _(cps->which), cps->program, cps->host, message);
8397         RemoveInputSource(cps->isr);
8398         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8399             if(cps == &first) appData.noChessProgram = TRUE;
8400             DisplayError(buf1, 0);
8401         }
8402         return;
8403     }
8404
8405     /*
8406      * Look for hint output
8407      */
8408     if (sscanf(message, "Hint: %s", buf1) == 1) {
8409         if (cps == &first && hintRequested) {
8410             hintRequested = FALSE;
8411             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8412                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8413                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8414                                     PosFlags(forwardMostMove),
8415                                     fromY, fromX, toY, toX, promoChar, buf1);
8416                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8417                 DisplayInformation(buf2);
8418             } else {
8419                 /* Hint move could not be parsed!? */
8420               snprintf(buf2, sizeof(buf2),
8421                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8422                         buf1, _(cps->which));
8423                 DisplayError(buf2, 0);
8424             }
8425         } else {
8426           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8427         }
8428         return;
8429     }
8430
8431     /*
8432      * Ignore other messages if game is not in progress
8433      */
8434     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8435         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8436
8437     /*
8438      * look for win, lose, draw, or draw offer
8439      */
8440     if (strncmp(message, "1-0", 3) == 0) {
8441         char *p, *q, *r = "";
8442         p = strchr(message, '{');
8443         if (p) {
8444             q = strchr(p, '}');
8445             if (q) {
8446                 *q = NULLCHAR;
8447                 r = p + 1;
8448             }
8449         }
8450         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8451         return;
8452     } else if (strncmp(message, "0-1", 3) == 0) {
8453         char *p, *q, *r = "";
8454         p = strchr(message, '{');
8455         if (p) {
8456             q = strchr(p, '}');
8457             if (q) {
8458                 *q = NULLCHAR;
8459                 r = p + 1;
8460             }
8461         }
8462         /* Kludge for Arasan 4.1 bug */
8463         if (strcmp(r, "Black resigns") == 0) {
8464             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8465             return;
8466         }
8467         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8468         return;
8469     } else if (strncmp(message, "1/2", 3) == 0) {
8470         char *p, *q, *r = "";
8471         p = strchr(message, '{');
8472         if (p) {
8473             q = strchr(p, '}');
8474             if (q) {
8475                 *q = NULLCHAR;
8476                 r = p + 1;
8477             }
8478         }
8479
8480         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8481         return;
8482
8483     } else if (strncmp(message, "White resign", 12) == 0) {
8484         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8485         return;
8486     } else if (strncmp(message, "Black resign", 12) == 0) {
8487         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8488         return;
8489     } else if (strncmp(message, "White matches", 13) == 0 ||
8490                strncmp(message, "Black matches", 13) == 0   ) {
8491         /* [HGM] ignore GNUShogi noises */
8492         return;
8493     } else if (strncmp(message, "White", 5) == 0 &&
8494                message[5] != '(' &&
8495                StrStr(message, "Black") == NULL) {
8496         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8497         return;
8498     } else if (strncmp(message, "Black", 5) == 0 &&
8499                message[5] != '(') {
8500         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8501         return;
8502     } else if (strcmp(message, "resign") == 0 ||
8503                strcmp(message, "computer resigns") == 0) {
8504         switch (gameMode) {
8505           case MachinePlaysBlack:
8506           case IcsPlayingBlack:
8507             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8508             break;
8509           case MachinePlaysWhite:
8510           case IcsPlayingWhite:
8511             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8512             break;
8513           case TwoMachinesPlay:
8514             if (cps->twoMachinesColor[0] == 'w')
8515               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8516             else
8517               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8518             break;
8519           default:
8520             /* can't happen */
8521             break;
8522         }
8523         return;
8524     } else if (strncmp(message, "opponent mates", 14) == 0) {
8525         switch (gameMode) {
8526           case MachinePlaysBlack:
8527           case IcsPlayingBlack:
8528             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8529             break;
8530           case MachinePlaysWhite:
8531           case IcsPlayingWhite:
8532             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8533             break;
8534           case TwoMachinesPlay:
8535             if (cps->twoMachinesColor[0] == 'w')
8536               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8537             else
8538               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539             break;
8540           default:
8541             /* can't happen */
8542             break;
8543         }
8544         return;
8545     } else if (strncmp(message, "computer mates", 14) == 0) {
8546         switch (gameMode) {
8547           case MachinePlaysBlack:
8548           case IcsPlayingBlack:
8549             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8550             break;
8551           case MachinePlaysWhite:
8552           case IcsPlayingWhite:
8553             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8554             break;
8555           case TwoMachinesPlay:
8556             if (cps->twoMachinesColor[0] == 'w')
8557               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8558             else
8559               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8560             break;
8561           default:
8562             /* can't happen */
8563             break;
8564         }
8565         return;
8566     } else if (strncmp(message, "checkmate", 9) == 0) {
8567         if (WhiteOnMove(forwardMostMove)) {
8568             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8569         } else {
8570             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8571         }
8572         return;
8573     } else if (strstr(message, "Draw") != NULL ||
8574                strstr(message, "game is a draw") != NULL) {
8575         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8576         return;
8577     } else if (strstr(message, "offer") != NULL &&
8578                strstr(message, "draw") != NULL) {
8579 #if ZIPPY
8580         if (appData.zippyPlay && first.initDone) {
8581             /* Relay offer to ICS */
8582             SendToICS(ics_prefix);
8583             SendToICS("draw\n");
8584         }
8585 #endif
8586         cps->offeredDraw = 2; /* valid until this engine moves twice */
8587         if (gameMode == TwoMachinesPlay) {
8588             if (cps->other->offeredDraw) {
8589                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8590             /* [HGM] in two-machine mode we delay relaying draw offer      */
8591             /* until after we also have move, to see if it is really claim */
8592             }
8593         } else if (gameMode == MachinePlaysWhite ||
8594                    gameMode == MachinePlaysBlack) {
8595           if (userOfferedDraw) {
8596             DisplayInformation(_("Machine accepts your draw offer"));
8597             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8598           } else {
8599             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8600           }
8601         }
8602     }
8603
8604
8605     /*
8606      * Look for thinking output
8607      */
8608     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8609           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8610                                 ) {
8611         int plylev, mvleft, mvtot, curscore, time;
8612         char mvname[MOVE_LEN];
8613         u64 nodes; // [DM]
8614         char plyext;
8615         int ignore = FALSE;
8616         int prefixHint = FALSE;
8617         mvname[0] = NULLCHAR;
8618
8619         switch (gameMode) {
8620           case MachinePlaysBlack:
8621           case IcsPlayingBlack:
8622             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8623             break;
8624           case MachinePlaysWhite:
8625           case IcsPlayingWhite:
8626             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8627             break;
8628           case AnalyzeMode:
8629           case AnalyzeFile:
8630             break;
8631           case IcsObserving: /* [DM] icsEngineAnalyze */
8632             if (!appData.icsEngineAnalyze) ignore = TRUE;
8633             break;
8634           case TwoMachinesPlay:
8635             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8636                 ignore = TRUE;
8637             }
8638             break;
8639           default:
8640             ignore = TRUE;
8641             break;
8642         }
8643
8644         if (!ignore) {
8645             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8646             buf1[0] = NULLCHAR;
8647             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8648                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8649
8650                 if (plyext != ' ' && plyext != '\t') {
8651                     time *= 100;
8652                 }
8653
8654                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8655                 if( cps->scoreIsAbsolute &&
8656                     ( gameMode == MachinePlaysBlack ||
8657                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8658                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8659                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8660                      !WhiteOnMove(currentMove)
8661                     ) )
8662                 {
8663                     curscore = -curscore;
8664                 }
8665
8666                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8667
8668                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8669                         char buf[MSG_SIZ];
8670                         FILE *f;
8671                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8672                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8673                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8674                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8675                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8676                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8677                                 fclose(f);
8678                         } else DisplayError(_("failed writing PV"), 0);
8679                 }
8680
8681                 tempStats.depth = plylev;
8682                 tempStats.nodes = nodes;
8683                 tempStats.time = time;
8684                 tempStats.score = curscore;
8685                 tempStats.got_only_move = 0;
8686
8687                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8688                         int ticklen;
8689
8690                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8691                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8692                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8693                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8694                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8695                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8696                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8697                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8698                 }
8699
8700                 /* Buffer overflow protection */
8701                 if (pv[0] != NULLCHAR) {
8702                     if (strlen(pv) >= sizeof(tempStats.movelist)
8703                         && appData.debugMode) {
8704                         fprintf(debugFP,
8705                                 "PV is too long; using the first %u bytes.\n",
8706                                 (unsigned) sizeof(tempStats.movelist) - 1);
8707                     }
8708
8709                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8710                 } else {
8711                     sprintf(tempStats.movelist, " no PV\n");
8712                 }
8713
8714                 if (tempStats.seen_stat) {
8715                     tempStats.ok_to_send = 1;
8716                 }
8717
8718                 if (strchr(tempStats.movelist, '(') != NULL) {
8719                     tempStats.line_is_book = 1;
8720                     tempStats.nr_moves = 0;
8721                     tempStats.moves_left = 0;
8722                 } else {
8723                     tempStats.line_is_book = 0;
8724                 }
8725
8726                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8727                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8728
8729                 SendProgramStatsToFrontend( cps, &tempStats );
8730
8731                 /*
8732                     [AS] Protect the thinkOutput buffer from overflow... this
8733                     is only useful if buf1 hasn't overflowed first!
8734                 */
8735                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8736                          plylev,
8737                          (gameMode == TwoMachinesPlay ?
8738                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8739                          ((double) curscore) / 100.0,
8740                          prefixHint ? lastHint : "",
8741                          prefixHint ? " " : "" );
8742
8743                 if( buf1[0] != NULLCHAR ) {
8744                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8745
8746                     if( strlen(pv) > max_len ) {
8747                         if( appData.debugMode) {
8748                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8749                         }
8750                         pv[max_len+1] = '\0';
8751                     }
8752
8753                     strcat( thinkOutput, pv);
8754                 }
8755
8756                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8757                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8758                     DisplayMove(currentMove - 1);
8759                 }
8760                 return;
8761
8762             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8763                 /* crafty (9.25+) says "(only move) <move>"
8764                  * if there is only 1 legal move
8765                  */
8766                 sscanf(p, "(only move) %s", buf1);
8767                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8768                 sprintf(programStats.movelist, "%s (only move)", buf1);
8769                 programStats.depth = 1;
8770                 programStats.nr_moves = 1;
8771                 programStats.moves_left = 1;
8772                 programStats.nodes = 1;
8773                 programStats.time = 1;
8774                 programStats.got_only_move = 1;
8775
8776                 /* Not really, but we also use this member to
8777                    mean "line isn't going to change" (Crafty
8778                    isn't searching, so stats won't change) */
8779                 programStats.line_is_book = 1;
8780
8781                 SendProgramStatsToFrontend( cps, &programStats );
8782
8783                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8784                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8785                     DisplayMove(currentMove - 1);
8786                 }
8787                 return;
8788             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8789                               &time, &nodes, &plylev, &mvleft,
8790                               &mvtot, mvname) >= 5) {
8791                 /* The stat01: line is from Crafty (9.29+) in response
8792                    to the "." command */
8793                 programStats.seen_stat = 1;
8794                 cps->maybeThinking = TRUE;
8795
8796                 if (programStats.got_only_move || !appData.periodicUpdates)
8797                   return;
8798
8799                 programStats.depth = plylev;
8800                 programStats.time = time;
8801                 programStats.nodes = nodes;
8802                 programStats.moves_left = mvleft;
8803                 programStats.nr_moves = mvtot;
8804                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8805                 programStats.ok_to_send = 1;
8806                 programStats.movelist[0] = '\0';
8807
8808                 SendProgramStatsToFrontend( cps, &programStats );
8809
8810                 return;
8811
8812             } else if (strncmp(message,"++",2) == 0) {
8813                 /* Crafty 9.29+ outputs this */
8814                 programStats.got_fail = 2;
8815                 return;
8816
8817             } else if (strncmp(message,"--",2) == 0) {
8818                 /* Crafty 9.29+ outputs this */
8819                 programStats.got_fail = 1;
8820                 return;
8821
8822             } else if (thinkOutput[0] != NULLCHAR &&
8823                        strncmp(message, "    ", 4) == 0) {
8824                 unsigned message_len;
8825
8826                 p = message;
8827                 while (*p && *p == ' ') p++;
8828
8829                 message_len = strlen( p );
8830
8831                 /* [AS] Avoid buffer overflow */
8832                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8833                     strcat(thinkOutput, " ");
8834                     strcat(thinkOutput, p);
8835                 }
8836
8837                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8838                     strcat(programStats.movelist, " ");
8839                     strcat(programStats.movelist, p);
8840                 }
8841
8842                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8843                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8844                     DisplayMove(currentMove - 1);
8845                 }
8846                 return;
8847             }
8848         }
8849         else {
8850             buf1[0] = NULLCHAR;
8851
8852             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8853                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8854             {
8855                 ChessProgramStats cpstats;
8856
8857                 if (plyext != ' ' && plyext != '\t') {
8858                     time *= 100;
8859                 }
8860
8861                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8862                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8863                     curscore = -curscore;
8864                 }
8865
8866                 cpstats.depth = plylev;
8867                 cpstats.nodes = nodes;
8868                 cpstats.time = time;
8869                 cpstats.score = curscore;
8870                 cpstats.got_only_move = 0;
8871                 cpstats.movelist[0] = '\0';
8872
8873                 if (buf1[0] != NULLCHAR) {
8874                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8875                 }
8876
8877                 cpstats.ok_to_send = 0;
8878                 cpstats.line_is_book = 0;
8879                 cpstats.nr_moves = 0;
8880                 cpstats.moves_left = 0;
8881
8882                 SendProgramStatsToFrontend( cps, &cpstats );
8883             }
8884         }
8885     }
8886 }
8887
8888
8889 /* Parse a game score from the character string "game", and
8890    record it as the history of the current game.  The game
8891    score is NOT assumed to start from the standard position.
8892    The display is not updated in any way.
8893    */
8894 void
8895 ParseGameHistory(game)
8896      char *game;
8897 {
8898     ChessMove moveType;
8899     int fromX, fromY, toX, toY, boardIndex;
8900     char promoChar;
8901     char *p, *q;
8902     char buf[MSG_SIZ];
8903
8904     if (appData.debugMode)
8905       fprintf(debugFP, "Parsing game history: %s\n", game);
8906
8907     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8908     gameInfo.site = StrSave(appData.icsHost);
8909     gameInfo.date = PGNDate();
8910     gameInfo.round = StrSave("-");
8911
8912     /* Parse out names of players */
8913     while (*game == ' ') game++;
8914     p = buf;
8915     while (*game != ' ') *p++ = *game++;
8916     *p = NULLCHAR;
8917     gameInfo.white = StrSave(buf);
8918     while (*game == ' ') game++;
8919     p = buf;
8920     while (*game != ' ' && *game != '\n') *p++ = *game++;
8921     *p = NULLCHAR;
8922     gameInfo.black = StrSave(buf);
8923
8924     /* Parse moves */
8925     boardIndex = blackPlaysFirst ? 1 : 0;
8926     yynewstr(game);
8927     for (;;) {
8928         yyboardindex = boardIndex;
8929         moveType = (ChessMove) Myylex();
8930         switch (moveType) {
8931           case IllegalMove:             /* maybe suicide chess, etc. */
8932   if (appData.debugMode) {
8933     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8935     setbuf(debugFP, NULL);
8936   }
8937           case WhitePromotion:
8938           case BlackPromotion:
8939           case WhiteNonPromotion:
8940           case BlackNonPromotion:
8941           case NormalMove:
8942           case WhiteCapturesEnPassant:
8943           case BlackCapturesEnPassant:
8944           case WhiteKingSideCastle:
8945           case WhiteQueenSideCastle:
8946           case BlackKingSideCastle:
8947           case BlackQueenSideCastle:
8948           case WhiteKingSideCastleWild:
8949           case WhiteQueenSideCastleWild:
8950           case BlackKingSideCastleWild:
8951           case BlackQueenSideCastleWild:
8952           /* PUSH Fabien */
8953           case WhiteHSideCastleFR:
8954           case WhiteASideCastleFR:
8955           case BlackHSideCastleFR:
8956           case BlackASideCastleFR:
8957           /* POP Fabien */
8958             fromX = currentMoveString[0] - AAA;
8959             fromY = currentMoveString[1] - ONE;
8960             toX = currentMoveString[2] - AAA;
8961             toY = currentMoveString[3] - ONE;
8962             promoChar = currentMoveString[4];
8963             break;
8964           case WhiteDrop:
8965           case BlackDrop:
8966             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8967             fromX = moveType == WhiteDrop ?
8968               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8969             (int) CharToPiece(ToLower(currentMoveString[0]));
8970             fromY = DROP_RANK;
8971             toX = currentMoveString[2] - AAA;
8972             toY = currentMoveString[3] - ONE;
8973             promoChar = NULLCHAR;
8974             break;
8975           case AmbiguousMove:
8976             /* bug? */
8977             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8978   if (appData.debugMode) {
8979     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8980     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8981     setbuf(debugFP, NULL);
8982   }
8983             DisplayError(buf, 0);
8984             return;
8985           case ImpossibleMove:
8986             /* bug? */
8987             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8988   if (appData.debugMode) {
8989     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8990     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8991     setbuf(debugFP, NULL);
8992   }
8993             DisplayError(buf, 0);
8994             return;
8995           case EndOfFile:
8996             if (boardIndex < backwardMostMove) {
8997                 /* Oops, gap.  How did that happen? */
8998                 DisplayError(_("Gap in move list"), 0);
8999                 return;
9000             }
9001             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9002             if (boardIndex > forwardMostMove) {
9003                 forwardMostMove = boardIndex;
9004             }
9005             return;
9006           case ElapsedTime:
9007             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9008                 strcat(parseList[boardIndex-1], " ");
9009                 strcat(parseList[boardIndex-1], yy_text);
9010             }
9011             continue;
9012           case Comment:
9013           case PGNTag:
9014           case NAG:
9015           default:
9016             /* ignore */
9017             continue;
9018           case WhiteWins:
9019           case BlackWins:
9020           case GameIsDrawn:
9021           case GameUnfinished:
9022             if (gameMode == IcsExamining) {
9023                 if (boardIndex < backwardMostMove) {
9024                     /* Oops, gap.  How did that happen? */
9025                     return;
9026                 }
9027                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9028                 return;
9029             }
9030             gameInfo.result = moveType;
9031             p = strchr(yy_text, '{');
9032             if (p == NULL) p = strchr(yy_text, '(');
9033             if (p == NULL) {
9034                 p = yy_text;
9035                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9036             } else {
9037                 q = strchr(p, *p == '{' ? '}' : ')');
9038                 if (q != NULL) *q = NULLCHAR;
9039                 p++;
9040             }
9041             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9042             gameInfo.resultDetails = StrSave(p);
9043             continue;
9044         }
9045         if (boardIndex >= forwardMostMove &&
9046             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9047             backwardMostMove = blackPlaysFirst ? 1 : 0;
9048             return;
9049         }
9050         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9051                                  fromY, fromX, toY, toX, promoChar,
9052                                  parseList[boardIndex]);
9053         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9054         /* currentMoveString is set as a side-effect of yylex */
9055         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9056         strcat(moveList[boardIndex], "\n");
9057         boardIndex++;
9058         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9059         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9060           case MT_NONE:
9061           case MT_STALEMATE:
9062           default:
9063             break;
9064           case MT_CHECK:
9065             if(gameInfo.variant != VariantShogi)
9066                 strcat(parseList[boardIndex - 1], "+");
9067             break;
9068           case MT_CHECKMATE:
9069           case MT_STAINMATE:
9070             strcat(parseList[boardIndex - 1], "#");
9071             break;
9072         }
9073     }
9074 }
9075
9076
9077 /* Apply a move to the given board  */
9078 void
9079 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9080      int fromX, fromY, toX, toY;
9081      int promoChar;
9082      Board board;
9083 {
9084   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9085   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9086
9087     /* [HGM] compute & store e.p. status and castling rights for new position */
9088     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9089
9090       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9091       oldEP = (signed char)board[EP_STATUS];
9092       board[EP_STATUS] = EP_NONE;
9093
9094   if (fromY == DROP_RANK) {
9095         /* must be first */
9096         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9097             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9098             return;
9099         }
9100         piece = board[toY][toX] = (ChessSquare) fromX;
9101   } else {
9102       int i;
9103
9104       if( board[toY][toX] != EmptySquare )
9105            board[EP_STATUS] = EP_CAPTURE;
9106
9107       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9108            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9109                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9110       } else
9111       if( board[fromY][fromX] == WhitePawn ) {
9112            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9113                board[EP_STATUS] = EP_PAWN_MOVE;
9114            if( toY-fromY==2) {
9115                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9116                         gameInfo.variant != VariantBerolina || toX < fromX)
9117                       board[EP_STATUS] = toX | berolina;
9118                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9119                         gameInfo.variant != VariantBerolina || toX > fromX)
9120                       board[EP_STATUS] = toX;
9121            }
9122       } else
9123       if( board[fromY][fromX] == BlackPawn ) {
9124            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9125                board[EP_STATUS] = EP_PAWN_MOVE;
9126            if( toY-fromY== -2) {
9127                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9128                         gameInfo.variant != VariantBerolina || toX < fromX)
9129                       board[EP_STATUS] = toX | berolina;
9130                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9131                         gameInfo.variant != VariantBerolina || toX > fromX)
9132                       board[EP_STATUS] = toX;
9133            }
9134        }
9135
9136        for(i=0; i<nrCastlingRights; i++) {
9137            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9138               board[CASTLING][i] == toX   && castlingRank[i] == toY
9139              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9140        }
9141
9142      if (fromX == toX && fromY == toY) return;
9143
9144      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9145      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9146      if(gameInfo.variant == VariantKnightmate)
9147          king += (int) WhiteUnicorn - (int) WhiteKing;
9148
9149     /* Code added by Tord: */
9150     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9151     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9152         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9153       board[fromY][fromX] = EmptySquare;
9154       board[toY][toX] = EmptySquare;
9155       if((toX > fromX) != (piece == WhiteRook)) {
9156         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9157       } else {
9158         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9159       }
9160     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9161                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9162       board[fromY][fromX] = EmptySquare;
9163       board[toY][toX] = EmptySquare;
9164       if((toX > fromX) != (piece == BlackRook)) {
9165         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9166       } else {
9167         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9168       }
9169     /* End of code added by Tord */
9170
9171     } else if (board[fromY][fromX] == king
9172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9173         && toY == fromY && toX > fromX+1) {
9174         board[fromY][fromX] = EmptySquare;
9175         board[toY][toX] = king;
9176         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9177         board[fromY][BOARD_RGHT-1] = EmptySquare;
9178     } else if (board[fromY][fromX] == king
9179         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9180                && toY == fromY && toX < fromX-1) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = king;
9183         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9184         board[fromY][BOARD_LEFT] = EmptySquare;
9185     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9186                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9187                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9188                ) {
9189         /* white pawn promotion */
9190         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9191         if(gameInfo.variant==VariantBughouse ||
9192            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9193             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9194         board[fromY][fromX] = EmptySquare;
9195     } else if ((fromY >= BOARD_HEIGHT>>1)
9196                && (toX != fromX)
9197                && gameInfo.variant != VariantXiangqi
9198                && gameInfo.variant != VariantBerolina
9199                && (board[fromY][fromX] == WhitePawn)
9200                && (board[toY][toX] == EmptySquare)) {
9201         board[fromY][fromX] = EmptySquare;
9202         board[toY][toX] = WhitePawn;
9203         captured = board[toY - 1][toX];
9204         board[toY - 1][toX] = EmptySquare;
9205     } else if ((fromY == BOARD_HEIGHT-4)
9206                && (toX == fromX)
9207                && gameInfo.variant == VariantBerolina
9208                && (board[fromY][fromX] == WhitePawn)
9209                && (board[toY][toX] == EmptySquare)) {
9210         board[fromY][fromX] = EmptySquare;
9211         board[toY][toX] = WhitePawn;
9212         if(oldEP & EP_BEROLIN_A) {
9213                 captured = board[fromY][fromX-1];
9214                 board[fromY][fromX-1] = EmptySquare;
9215         }else{  captured = board[fromY][fromX+1];
9216                 board[fromY][fromX+1] = EmptySquare;
9217         }
9218     } else if (board[fromY][fromX] == king
9219         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9220                && toY == fromY && toX > fromX+1) {
9221         board[fromY][fromX] = EmptySquare;
9222         board[toY][toX] = king;
9223         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9224         board[fromY][BOARD_RGHT-1] = EmptySquare;
9225     } else if (board[fromY][fromX] == king
9226         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9227                && toY == fromY && toX < fromX-1) {
9228         board[fromY][fromX] = EmptySquare;
9229         board[toY][toX] = king;
9230         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9231         board[fromY][BOARD_LEFT] = EmptySquare;
9232     } else if (fromY == 7 && fromX == 3
9233                && board[fromY][fromX] == BlackKing
9234                && toY == 7 && toX == 5) {
9235         board[fromY][fromX] = EmptySquare;
9236         board[toY][toX] = BlackKing;
9237         board[fromY][7] = EmptySquare;
9238         board[toY][4] = BlackRook;
9239     } else if (fromY == 7 && fromX == 3
9240                && board[fromY][fromX] == BlackKing
9241                && toY == 7 && toX == 1) {
9242         board[fromY][fromX] = EmptySquare;
9243         board[toY][toX] = BlackKing;
9244         board[fromY][0] = EmptySquare;
9245         board[toY][2] = BlackRook;
9246     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9247                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9248                && toY < promoRank && promoChar
9249                ) {
9250         /* black pawn promotion */
9251         board[toY][toX] = CharToPiece(ToLower(promoChar));
9252         if(gameInfo.variant==VariantBughouse ||
9253            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9254             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9255         board[fromY][fromX] = EmptySquare;
9256     } else if ((fromY < BOARD_HEIGHT>>1)
9257                && (toX != fromX)
9258                && gameInfo.variant != VariantXiangqi
9259                && gameInfo.variant != VariantBerolina
9260                && (board[fromY][fromX] == BlackPawn)
9261                && (board[toY][toX] == EmptySquare)) {
9262         board[fromY][fromX] = EmptySquare;
9263         board[toY][toX] = BlackPawn;
9264         captured = board[toY + 1][toX];
9265         board[toY + 1][toX] = EmptySquare;
9266     } else if ((fromY == 3)
9267                && (toX == fromX)
9268                && gameInfo.variant == VariantBerolina
9269                && (board[fromY][fromX] == BlackPawn)
9270                && (board[toY][toX] == EmptySquare)) {
9271         board[fromY][fromX] = EmptySquare;
9272         board[toY][toX] = BlackPawn;
9273         if(oldEP & EP_BEROLIN_A) {
9274                 captured = board[fromY][fromX-1];
9275                 board[fromY][fromX-1] = EmptySquare;
9276         }else{  captured = board[fromY][fromX+1];
9277                 board[fromY][fromX+1] = EmptySquare;
9278         }
9279     } else {
9280         board[toY][toX] = board[fromY][fromX];
9281         board[fromY][fromX] = EmptySquare;
9282     }
9283   }
9284
9285     if (gameInfo.holdingsWidth != 0) {
9286
9287       /* !!A lot more code needs to be written to support holdings  */
9288       /* [HGM] OK, so I have written it. Holdings are stored in the */
9289       /* penultimate board files, so they are automaticlly stored   */
9290       /* in the game history.                                       */
9291       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9292                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9293         /* Delete from holdings, by decreasing count */
9294         /* and erasing image if necessary            */
9295         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9296         if(p < (int) BlackPawn) { /* white drop */
9297              p -= (int)WhitePawn;
9298                  p = PieceToNumber((ChessSquare)p);
9299              if(p >= gameInfo.holdingsSize) p = 0;
9300              if(--board[p][BOARD_WIDTH-2] <= 0)
9301                   board[p][BOARD_WIDTH-1] = EmptySquare;
9302              if((int)board[p][BOARD_WIDTH-2] < 0)
9303                         board[p][BOARD_WIDTH-2] = 0;
9304         } else {                  /* black drop */
9305              p -= (int)BlackPawn;
9306                  p = PieceToNumber((ChessSquare)p);
9307              if(p >= gameInfo.holdingsSize) p = 0;
9308              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9309                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9310              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9311                         board[BOARD_HEIGHT-1-p][1] = 0;
9312         }
9313       }
9314       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9315           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9316         /* [HGM] holdings: Add to holdings, if holdings exist */
9317         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9318                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9319                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9320         }
9321         p = (int) captured;
9322         if (p >= (int) BlackPawn) {
9323           p -= (int)BlackPawn;
9324           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9325                   /* in Shogi restore piece to its original  first */
9326                   captured = (ChessSquare) (DEMOTED captured);
9327                   p = DEMOTED p;
9328           }
9329           p = PieceToNumber((ChessSquare)p);
9330           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9331           board[p][BOARD_WIDTH-2]++;
9332           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9333         } else {
9334           p -= (int)WhitePawn;
9335           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9336                   captured = (ChessSquare) (DEMOTED captured);
9337                   p = DEMOTED p;
9338           }
9339           p = PieceToNumber((ChessSquare)p);
9340           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9341           board[BOARD_HEIGHT-1-p][1]++;
9342           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9343         }
9344       }
9345     } else if (gameInfo.variant == VariantAtomic) {
9346       if (captured != EmptySquare) {
9347         int y, x;
9348         for (y = toY-1; y <= toY+1; y++) {
9349           for (x = toX-1; x <= toX+1; x++) {
9350             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9351                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9352               board[y][x] = EmptySquare;
9353             }
9354           }
9355         }
9356         board[toY][toX] = EmptySquare;
9357       }
9358     }
9359     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9360         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9361     } else
9362     if(promoChar == '+') {
9363         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9364         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9365     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9366         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9367     }
9368     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9369                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9370         // [HGM] superchess: take promotion piece out of holdings
9371         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9372         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9373             if(!--board[k][BOARD_WIDTH-2])
9374                 board[k][BOARD_WIDTH-1] = EmptySquare;
9375         } else {
9376             if(!--board[BOARD_HEIGHT-1-k][1])
9377                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9378         }
9379     }
9380
9381 }
9382
9383 /* Updates forwardMostMove */
9384 void
9385 MakeMove(fromX, fromY, toX, toY, promoChar)
9386      int fromX, fromY, toX, toY;
9387      int promoChar;
9388 {
9389 //    forwardMostMove++; // [HGM] bare: moved downstream
9390
9391     (void) CoordsToAlgebraic(boards[forwardMostMove],
9392                              PosFlags(forwardMostMove),
9393                              fromY, fromX, toY, toX, promoChar,
9394                              parseList[forwardMostMove]);
9395
9396     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9397         int timeLeft; static int lastLoadFlag=0; int king, piece;
9398         piece = boards[forwardMostMove][fromY][fromX];
9399         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9400         if(gameInfo.variant == VariantKnightmate)
9401             king += (int) WhiteUnicorn - (int) WhiteKing;
9402         if(forwardMostMove == 0) {
9403             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9404                 fprintf(serverMoves, "%s;", UserName());
9405             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9406                 fprintf(serverMoves, "%s;", second.tidy);
9407             fprintf(serverMoves, "%s;", first.tidy);
9408             if(gameMode == MachinePlaysWhite)
9409                 fprintf(serverMoves, "%s;", UserName());
9410             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9411                 fprintf(serverMoves, "%s;", second.tidy);
9412         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9413         lastLoadFlag = loadFlag;
9414         // print base move
9415         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9416         // print castling suffix
9417         if( toY == fromY && piece == king ) {
9418             if(toX-fromX > 1)
9419                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9420             if(fromX-toX >1)
9421                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9422         }
9423         // e.p. suffix
9424         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9425              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9426              boards[forwardMostMove][toY][toX] == EmptySquare
9427              && fromX != toX && fromY != toY)
9428                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9429         // promotion suffix
9430         if(promoChar != NULLCHAR)
9431                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9432         if(!loadFlag) {
9433                 char buf[MOVE_LEN*2], *p; int len;
9434             fprintf(serverMoves, "/%d/%d",
9435                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9436             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9437             else                      timeLeft = blackTimeRemaining/1000;
9438             fprintf(serverMoves, "/%d", timeLeft);
9439                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9440                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9441                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9442             fprintf(serverMoves, "/%s", buf);
9443         }
9444         fflush(serverMoves);
9445     }
9446
9447     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9448         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9449       return;
9450     }
9451     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9452     if (commentList[forwardMostMove+1] != NULL) {
9453         free(commentList[forwardMostMove+1]);
9454         commentList[forwardMostMove+1] = NULL;
9455     }
9456     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9457     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9458     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9459     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9460     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9461     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9462     adjustedClock = FALSE;
9463     gameInfo.result = GameUnfinished;
9464     if (gameInfo.resultDetails != NULL) {
9465         free(gameInfo.resultDetails);
9466         gameInfo.resultDetails = NULL;
9467     }
9468     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9469                               moveList[forwardMostMove - 1]);
9470     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9471       case MT_NONE:
9472       case MT_STALEMATE:
9473       default:
9474         break;
9475       case MT_CHECK:
9476         if(gameInfo.variant != VariantShogi)
9477             strcat(parseList[forwardMostMove - 1], "+");
9478         break;
9479       case MT_CHECKMATE:
9480       case MT_STAINMATE:
9481         strcat(parseList[forwardMostMove - 1], "#");
9482         break;
9483     }
9484     if (appData.debugMode) {
9485         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9486     }
9487
9488 }
9489
9490 /* Updates currentMove if not pausing */
9491 void
9492 ShowMove(fromX, fromY, toX, toY)
9493 {
9494     int instant = (gameMode == PlayFromGameFile) ?
9495         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9496     if(appData.noGUI) return;
9497     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9498         if (!instant) {
9499             if (forwardMostMove == currentMove + 1) {
9500                 AnimateMove(boards[forwardMostMove - 1],
9501                             fromX, fromY, toX, toY);
9502             }
9503             if (appData.highlightLastMove) {
9504                 SetHighlights(fromX, fromY, toX, toY);
9505             }
9506         }
9507         currentMove = forwardMostMove;
9508     }
9509
9510     if (instant) return;
9511
9512     DisplayMove(currentMove - 1);
9513     DrawPosition(FALSE, boards[currentMove]);
9514     DisplayBothClocks();
9515     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9516 }
9517
9518 void SendEgtPath(ChessProgramState *cps)
9519 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9520         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9521
9522         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9523
9524         while(*p) {
9525             char c, *q = name+1, *r, *s;
9526
9527             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9528             while(*p && *p != ',') *q++ = *p++;
9529             *q++ = ':'; *q = 0;
9530             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9531                 strcmp(name, ",nalimov:") == 0 ) {
9532                 // take nalimov path from the menu-changeable option first, if it is defined
9533               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9534                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9535             } else
9536             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9537                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9538                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9539                 s = r = StrStr(s, ":") + 1; // beginning of path info
9540                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9541                 c = *r; *r = 0;             // temporarily null-terminate path info
9542                     *--q = 0;               // strip of trailig ':' from name
9543                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9544                 *r = c;
9545                 SendToProgram(buf,cps);     // send egtbpath command for this format
9546             }
9547             if(*p == ',') p++; // read away comma to position for next format name
9548         }
9549 }
9550
9551 void
9552 InitChessProgram(cps, setup)
9553      ChessProgramState *cps;
9554      int setup; /* [HGM] needed to setup FRC opening position */
9555 {
9556     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9557     if (appData.noChessProgram) return;
9558     hintRequested = FALSE;
9559     bookRequested = FALSE;
9560
9561     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9562     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9563     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9564     if(cps->memSize) { /* [HGM] memory */
9565       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9566         SendToProgram(buf, cps);
9567     }
9568     SendEgtPath(cps); /* [HGM] EGT */
9569     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9570       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9571         SendToProgram(buf, cps);
9572     }
9573
9574     SendToProgram(cps->initString, cps);
9575     if (gameInfo.variant != VariantNormal &&
9576         gameInfo.variant != VariantLoadable
9577         /* [HGM] also send variant if board size non-standard */
9578         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9579                                             ) {
9580       char *v = VariantName(gameInfo.variant);
9581       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9582         /* [HGM] in protocol 1 we have to assume all variants valid */
9583         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9584         DisplayFatalError(buf, 0, 1);
9585         return;
9586       }
9587
9588       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9589       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9590       if( gameInfo.variant == VariantXiangqi )
9591            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9592       if( gameInfo.variant == VariantShogi )
9593            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9594       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9595            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9596       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9597           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9598            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9599       if( gameInfo.variant == VariantCourier )
9600            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9601       if( gameInfo.variant == VariantSuper )
9602            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9603       if( gameInfo.variant == VariantGreat )
9604            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9605       if( gameInfo.variant == VariantSChess )
9606            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9607       if( gameInfo.variant == VariantGrand )
9608            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9609
9610       if(overruled) {
9611         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9612                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9613            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9614            if(StrStr(cps->variants, b) == NULL) {
9615                // specific sized variant not known, check if general sizing allowed
9616                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9617                    if(StrStr(cps->variants, "boardsize") == NULL) {
9618                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9619                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9620                        DisplayFatalError(buf, 0, 1);
9621                        return;
9622                    }
9623                    /* [HGM] here we really should compare with the maximum supported board size */
9624                }
9625            }
9626       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9627       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9628       SendToProgram(buf, cps);
9629     }
9630     currentlyInitializedVariant = gameInfo.variant;
9631
9632     /* [HGM] send opening position in FRC to first engine */
9633     if(setup) {
9634           SendToProgram("force\n", cps);
9635           SendBoard(cps, 0);
9636           /* engine is now in force mode! Set flag to wake it up after first move. */
9637           setboardSpoiledMachineBlack = 1;
9638     }
9639
9640     if (cps->sendICS) {
9641       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9642       SendToProgram(buf, cps);
9643     }
9644     cps->maybeThinking = FALSE;
9645     cps->offeredDraw = 0;
9646     if (!appData.icsActive) {
9647         SendTimeControl(cps, movesPerSession, timeControl,
9648                         timeIncrement, appData.searchDepth,
9649                         searchTime);
9650     }
9651     if (appData.showThinking
9652         // [HGM] thinking: four options require thinking output to be sent
9653         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9654                                 ) {
9655         SendToProgram("post\n", cps);
9656     }
9657     SendToProgram("hard\n", cps);
9658     if (!appData.ponderNextMove) {
9659         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9660            it without being sure what state we are in first.  "hard"
9661            is not a toggle, so that one is OK.
9662          */
9663         SendToProgram("easy\n", cps);
9664     }
9665     if (cps->usePing) {
9666       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9667       SendToProgram(buf, cps);
9668     }
9669     cps->initDone = TRUE;
9670     ClearEngineOutputPane(cps == &second);
9671 }
9672
9673
9674 void
9675 StartChessProgram(cps)
9676      ChessProgramState *cps;
9677 {
9678     char buf[MSG_SIZ];
9679     int err;
9680
9681     if (appData.noChessProgram) return;
9682     cps->initDone = FALSE;
9683
9684     if (strcmp(cps->host, "localhost") == 0) {
9685         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9686     } else if (*appData.remoteShell == NULLCHAR) {
9687         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9688     } else {
9689         if (*appData.remoteUser == NULLCHAR) {
9690           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9691                     cps->program);
9692         } else {
9693           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9694                     cps->host, appData.remoteUser, cps->program);
9695         }
9696         err = StartChildProcess(buf, "", &cps->pr);
9697     }
9698
9699     if (err != 0) {
9700       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9701         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9702         if(cps != &first) return;
9703         appData.noChessProgram = TRUE;
9704         ThawUI();
9705         SetNCPMode();
9706 //      DisplayFatalError(buf, err, 1);
9707 //      cps->pr = NoProc;
9708 //      cps->isr = NULL;
9709         return;
9710     }
9711
9712     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9713     if (cps->protocolVersion > 1) {
9714       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9715       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9716       cps->comboCnt = 0;  //                and values of combo boxes
9717       SendToProgram(buf, cps);
9718     } else {
9719       SendToProgram("xboard\n", cps);
9720     }
9721 }
9722
9723 void
9724 TwoMachinesEventIfReady P((void))
9725 {
9726   static int curMess = 0;
9727   if (first.lastPing != first.lastPong) {
9728     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9729     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9730     return;
9731   }
9732   if (second.lastPing != second.lastPong) {
9733     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9734     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9735     return;
9736   }
9737   DisplayMessage("", ""); curMess = 0;
9738   ThawUI();
9739   TwoMachinesEvent();
9740 }
9741
9742 char *
9743 MakeName(char *template)
9744 {
9745     time_t clock;
9746     struct tm *tm;
9747     static char buf[MSG_SIZ];
9748     char *p = buf;
9749     int i;
9750
9751     clock = time((time_t *)NULL);
9752     tm = localtime(&clock);
9753
9754     while(*p++ = *template++) if(p[-1] == '%') {
9755         switch(*template++) {
9756           case 0:   *p = 0; return buf;
9757           case 'Y': i = tm->tm_year+1900; break;
9758           case 'y': i = tm->tm_year-100; break;
9759           case 'M': i = tm->tm_mon+1; break;
9760           case 'd': i = tm->tm_mday; break;
9761           case 'h': i = tm->tm_hour; break;
9762           case 'm': i = tm->tm_min; break;
9763           case 's': i = tm->tm_sec; break;
9764           default:  i = 0;
9765         }
9766         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9767     }
9768     return buf;
9769 }
9770
9771 int
9772 CountPlayers(char *p)
9773 {
9774     int n = 0;
9775     while(p = strchr(p, '\n')) p++, n++; // count participants
9776     return n;
9777 }
9778
9779 FILE *
9780 WriteTourneyFile(char *results, FILE *f)
9781 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9782     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9783     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9784         // create a file with tournament description
9785         fprintf(f, "-participants {%s}\n", appData.participants);
9786         fprintf(f, "-seedBase %d\n", appData.seedBase);
9787         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9788         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9789         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9790         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9791         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9792         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9793         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9794         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9795         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9796         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9797         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9798         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9799         if(searchTime > 0)
9800                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9801         else {
9802                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9803                 fprintf(f, "-tc %s\n", appData.timeControl);
9804                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9805         }
9806         fprintf(f, "-results \"%s\"\n", results);
9807     }
9808     return f;
9809 }
9810
9811 #define MAXENGINES 1000
9812 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9813
9814 void Substitute(char *participants, int expunge)
9815 {
9816     int i, changed, changes=0, nPlayers=0;
9817     char *p, *q, *r, buf[MSG_SIZ];
9818     if(participants == NULL) return;
9819     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9820     r = p = participants; q = appData.participants;
9821     while(*p && *p == *q) {
9822         if(*p == '\n') r = p+1, nPlayers++;
9823         p++; q++;
9824     }
9825     if(*p) { // difference
9826         while(*p && *p++ != '\n');
9827         while(*q && *q++ != '\n');
9828       changed = nPlayers;
9829         changes = 1 + (strcmp(p, q) != 0);
9830     }
9831     if(changes == 1) { // a single engine mnemonic was changed
9832         q = r; while(*q) nPlayers += (*q++ == '\n');
9833         p = buf; while(*r && (*p = *r++) != '\n') p++;
9834         *p = NULLCHAR;
9835         NamesToList(firstChessProgramNames, command, mnemonic);
9836         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9837         if(mnemonic[i]) { // The substitute is valid
9838             FILE *f;
9839             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9840                 flock(fileno(f), LOCK_EX);
9841                 ParseArgsFromFile(f);
9842                 fseek(f, 0, SEEK_SET);
9843                 FREE(appData.participants); appData.participants = participants;
9844                 if(expunge) { // erase results of replaced engine
9845                     int len = strlen(appData.results), w, b, dummy;
9846                     for(i=0; i<len; i++) {
9847                         Pairing(i, nPlayers, &w, &b, &dummy);
9848                         if((w == changed || b == changed) && appData.results[i] == '*') {
9849                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9850                             fclose(f);
9851                             return;
9852                         }
9853                     }
9854                     for(i=0; i<len; i++) {
9855                         Pairing(i, nPlayers, &w, &b, &dummy);
9856                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9857                     }
9858                 }
9859                 WriteTourneyFile(appData.results, f);
9860                 fclose(f); // release lock
9861                 return;
9862             }
9863         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9864     }
9865     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9866     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9867     free(participants);
9868     return;
9869 }
9870
9871 int
9872 CreateTourney(char *name)
9873 {
9874         FILE *f;
9875         if(matchMode && strcmp(name, appData.tourneyFile)) {
9876              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9877         }
9878         if(name[0] == NULLCHAR) {
9879             if(appData.participants[0])
9880                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9881             return 0;
9882         }
9883         f = fopen(name, "r");
9884         if(f) { // file exists
9885             ASSIGN(appData.tourneyFile, name);
9886             ParseArgsFromFile(f); // parse it
9887         } else {
9888             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9889             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9890                 DisplayError(_("Not enough participants"), 0);
9891                 return 0;
9892             }
9893             ASSIGN(appData.tourneyFile, name);
9894             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9895             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9896         }
9897         fclose(f);
9898         appData.noChessProgram = FALSE;
9899         appData.clockMode = TRUE;
9900         SetGNUMode();
9901         return 1;
9902 }
9903
9904 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9905 {
9906     char buf[MSG_SIZ], *p, *q;
9907     int i=1;
9908     while(*names) {
9909         p = names; q = buf;
9910         while(*p && *p != '\n') *q++ = *p++;
9911         *q = 0;
9912         if(engineList[i]) free(engineList[i]);
9913         engineList[i] = strdup(buf);
9914         if(*p == '\n') p++;
9915         TidyProgramName(engineList[i], "localhost", buf);
9916         if(engineMnemonic[i]) free(engineMnemonic[i]);
9917         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9918             strcat(buf, " (");
9919             sscanf(q + 8, "%s", buf + strlen(buf));
9920             strcat(buf, ")");
9921         }
9922         engineMnemonic[i] = strdup(buf);
9923         names = p; i++;
9924       if(i > MAXENGINES - 2) break;
9925     }
9926     engineList[i] = engineMnemonic[i] = NULL;
9927 }
9928
9929 // following implemented as macro to avoid type limitations
9930 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9931
9932 void SwapEngines(int n)
9933 {   // swap settings for first engine and other engine (so far only some selected options)
9934     int h;
9935     char *p;
9936     if(n == 0) return;
9937     SWAP(directory, p)
9938     SWAP(chessProgram, p)
9939     SWAP(isUCI, h)
9940     SWAP(hasOwnBookUCI, h)
9941     SWAP(protocolVersion, h)
9942     SWAP(reuse, h)
9943     SWAP(scoreIsAbsolute, h)
9944     SWAP(timeOdds, h)
9945     SWAP(logo, p)
9946     SWAP(pgnName, p)
9947     SWAP(pvSAN, h)
9948     SWAP(engOptions, p)
9949 }
9950
9951 void
9952 SetPlayer(int player)
9953 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9954     int i;
9955     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9956     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9957     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9958     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9959     if(mnemonic[i]) {
9960         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9961         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9962         appData.firstHasOwnBookUCI = !appData.defNoBook;
9963         ParseArgsFromString(buf);
9964     }
9965     free(engineName);
9966 }
9967
9968 int
9969 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9970 {   // determine players from game number
9971     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9972
9973     if(appData.tourneyType == 0) {
9974         roundsPerCycle = (nPlayers - 1) | 1;
9975         pairingsPerRound = nPlayers / 2;
9976     } else if(appData.tourneyType > 0) {
9977         roundsPerCycle = nPlayers - appData.tourneyType;
9978         pairingsPerRound = appData.tourneyType;
9979     }
9980     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9981     gamesPerCycle = gamesPerRound * roundsPerCycle;
9982     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9983     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9984     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9985     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9986     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9987     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9988
9989     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9990     if(appData.roundSync) *syncInterval = gamesPerRound;
9991
9992     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9993
9994     if(appData.tourneyType == 0) {
9995         if(curPairing == (nPlayers-1)/2 ) {
9996             *whitePlayer = curRound;
9997             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9998         } else {
9999             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10000             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10001             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10002             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10003         }
10004     } else if(appData.tourneyType > 0) {
10005         *whitePlayer = curPairing;
10006         *blackPlayer = curRound + appData.tourneyType;
10007     }
10008
10009     // take care of white/black alternation per round. 
10010     // For cycles and games this is already taken care of by default, derived from matchGame!
10011     return curRound & 1;
10012 }
10013
10014 int
10015 NextTourneyGame(int nr, int *swapColors)
10016 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10017     char *p, *q;
10018     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10019     FILE *tf;
10020     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10021     tf = fopen(appData.tourneyFile, "r");
10022     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10023     ParseArgsFromFile(tf); fclose(tf);
10024     InitTimeControls(); // TC might be altered from tourney file
10025
10026     nPlayers = CountPlayers(appData.participants); // count participants
10027     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10028     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10029
10030     if(syncInterval) {
10031         p = q = appData.results;
10032         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10033         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10034             DisplayMessage(_("Waiting for other game(s)"),"");
10035             waitingForGame = TRUE;
10036             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10037             return 0;
10038         }
10039         waitingForGame = FALSE;
10040     }
10041
10042     if(appData.tourneyType < 0) {
10043         if(nr>=0 && !pairingReceived) {
10044             char buf[1<<16];
10045             if(pairing.pr == NoProc) {
10046                 if(!appData.pairingEngine[0]) {
10047                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10048                     return 0;
10049                 }
10050                 StartChessProgram(&pairing); // starts the pairing engine
10051             }
10052             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10053             SendToProgram(buf, &pairing);
10054             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10055             SendToProgram(buf, &pairing);
10056             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10057         }
10058         pairingReceived = 0;                              // ... so we continue here 
10059         *swapColors = 0;
10060         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10061         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10062         matchGame = 1; roundNr = nr / syncInterval + 1;
10063     }
10064
10065     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10066
10067     // redefine engines, engine dir, etc.
10068     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10069     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10070     SwapEngines(1);
10071     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10072     SwapEngines(1);         // and make that valid for second engine by swapping
10073     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10074     InitEngine(&second, 1);
10075     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10076     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10077     return 1;
10078 }
10079
10080 void
10081 NextMatchGame()
10082 {   // performs game initialization that does not invoke engines, and then tries to start the game
10083     int res, firstWhite, swapColors = 0;
10084     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10085     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10086     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10087     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10088     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10089     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10090     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10091     Reset(FALSE, first.pr != NoProc);
10092     res = LoadGameOrPosition(matchGame); // setup game
10093     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10094     if(!res) return; // abort when bad game/pos file
10095     TwoMachinesEvent();
10096 }
10097
10098 void UserAdjudicationEvent( int result )
10099 {
10100     ChessMove gameResult = GameIsDrawn;
10101
10102     if( result > 0 ) {
10103         gameResult = WhiteWins;
10104     }
10105     else if( result < 0 ) {
10106         gameResult = BlackWins;
10107     }
10108
10109     if( gameMode == TwoMachinesPlay ) {
10110         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10111     }
10112 }
10113
10114
10115 // [HGM] save: calculate checksum of game to make games easily identifiable
10116 int StringCheckSum(char *s)
10117 {
10118         int i = 0;
10119         if(s==NULL) return 0;
10120         while(*s) i = i*259 + *s++;
10121         return i;
10122 }
10123
10124 int GameCheckSum()
10125 {
10126         int i, sum=0;
10127         for(i=backwardMostMove; i<forwardMostMove; i++) {
10128                 sum += pvInfoList[i].depth;
10129                 sum += StringCheckSum(parseList[i]);
10130                 sum += StringCheckSum(commentList[i]);
10131                 sum *= 261;
10132         }
10133         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10134         return sum + StringCheckSum(commentList[i]);
10135 } // end of save patch
10136
10137 void
10138 GameEnds(result, resultDetails, whosays)
10139      ChessMove result;
10140      char *resultDetails;
10141      int whosays;
10142 {
10143     GameMode nextGameMode;
10144     int isIcsGame;
10145     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10146
10147     if(endingGame) return; /* [HGM] crash: forbid recursion */
10148     endingGame = 1;
10149     if(twoBoards) { // [HGM] dual: switch back to one board
10150         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10151         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10152     }
10153     if (appData.debugMode) {
10154       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10155               result, resultDetails ? resultDetails : "(null)", whosays);
10156     }
10157
10158     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10159
10160     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10161         /* If we are playing on ICS, the server decides when the
10162            game is over, but the engine can offer to draw, claim
10163            a draw, or resign.
10164          */
10165 #if ZIPPY
10166         if (appData.zippyPlay && first.initDone) {
10167             if (result == GameIsDrawn) {
10168                 /* In case draw still needs to be claimed */
10169                 SendToICS(ics_prefix);
10170                 SendToICS("draw\n");
10171             } else if (StrCaseStr(resultDetails, "resign")) {
10172                 SendToICS(ics_prefix);
10173                 SendToICS("resign\n");
10174             }
10175         }
10176 #endif
10177         endingGame = 0; /* [HGM] crash */
10178         return;
10179     }
10180
10181     /* If we're loading the game from a file, stop */
10182     if (whosays == GE_FILE) {
10183       (void) StopLoadGameTimer();
10184       gameFileFP = NULL;
10185     }
10186
10187     /* Cancel draw offers */
10188     first.offeredDraw = second.offeredDraw = 0;
10189
10190     /* If this is an ICS game, only ICS can really say it's done;
10191        if not, anyone can. */
10192     isIcsGame = (gameMode == IcsPlayingWhite ||
10193                  gameMode == IcsPlayingBlack ||
10194                  gameMode == IcsObserving    ||
10195                  gameMode == IcsExamining);
10196
10197     if (!isIcsGame || whosays == GE_ICS) {
10198         /* OK -- not an ICS game, or ICS said it was done */
10199         StopClocks();
10200         if (!isIcsGame && !appData.noChessProgram)
10201           SetUserThinkingEnables();
10202
10203         /* [HGM] if a machine claims the game end we verify this claim */
10204         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10205             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10206                 char claimer;
10207                 ChessMove trueResult = (ChessMove) -1;
10208
10209                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10210                                             first.twoMachinesColor[0] :
10211                                             second.twoMachinesColor[0] ;
10212
10213                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10214                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10215                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10216                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10217                 } else
10218                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10219                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10220                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10221                 } else
10222                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10223                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10224                 }
10225
10226                 // now verify win claims, but not in drop games, as we don't understand those yet
10227                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10228                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10229                     (result == WhiteWins && claimer == 'w' ||
10230                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10231                       if (appData.debugMode) {
10232                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10233                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10234                       }
10235                       if(result != trueResult) {
10236                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10237                               result = claimer == 'w' ? BlackWins : WhiteWins;
10238                               resultDetails = buf;
10239                       }
10240                 } else
10241                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10242                     && (forwardMostMove <= backwardMostMove ||
10243                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10244                         (claimer=='b')==(forwardMostMove&1))
10245                                                                                   ) {
10246                       /* [HGM] verify: draws that were not flagged are false claims */
10247                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10248                       result = claimer == 'w' ? BlackWins : WhiteWins;
10249                       resultDetails = buf;
10250                 }
10251                 /* (Claiming a loss is accepted no questions asked!) */
10252             }
10253             /* [HGM] bare: don't allow bare King to win */
10254             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10255                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10256                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10257                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10258                && result != GameIsDrawn)
10259             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10260                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10261                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10262                         if(p >= 0 && p <= (int)WhiteKing) k++;
10263                 }
10264                 if (appData.debugMode) {
10265                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10266                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10267                 }
10268                 if(k <= 1) {
10269                         result = GameIsDrawn;
10270                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10271                         resultDetails = buf;
10272                 }
10273             }
10274         }
10275
10276
10277         if(serverMoves != NULL && !loadFlag) { char c = '=';
10278             if(result==WhiteWins) c = '+';
10279             if(result==BlackWins) c = '-';
10280             if(resultDetails != NULL)
10281                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10282         }
10283         if (resultDetails != NULL) {
10284             gameInfo.result = result;
10285             gameInfo.resultDetails = StrSave(resultDetails);
10286
10287             /* display last move only if game was not loaded from file */
10288             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10289                 DisplayMove(currentMove - 1);
10290
10291             if (forwardMostMove != 0) {
10292                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10293                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10294                                                                 ) {
10295                     if (*appData.saveGameFile != NULLCHAR) {
10296                         SaveGameToFile(appData.saveGameFile, TRUE);
10297                     } else if (appData.autoSaveGames) {
10298                         AutoSaveGame();
10299                     }
10300                     if (*appData.savePositionFile != NULLCHAR) {
10301                         SavePositionToFile(appData.savePositionFile);
10302                     }
10303                 }
10304             }
10305
10306             /* Tell program how game ended in case it is learning */
10307             /* [HGM] Moved this to after saving the PGN, just in case */
10308             /* engine died and we got here through time loss. In that */
10309             /* case we will get a fatal error writing the pipe, which */
10310             /* would otherwise lose us the PGN.                       */
10311             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10312             /* output during GameEnds should never be fatal anymore   */
10313             if (gameMode == MachinePlaysWhite ||
10314                 gameMode == MachinePlaysBlack ||
10315                 gameMode == TwoMachinesPlay ||
10316                 gameMode == IcsPlayingWhite ||
10317                 gameMode == IcsPlayingBlack ||
10318                 gameMode == BeginningOfGame) {
10319                 char buf[MSG_SIZ];
10320                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10321                         resultDetails);
10322                 if (first.pr != NoProc) {
10323                     SendToProgram(buf, &first);
10324                 }
10325                 if (second.pr != NoProc &&
10326                     gameMode == TwoMachinesPlay) {
10327                     SendToProgram(buf, &second);
10328                 }
10329             }
10330         }
10331
10332         if (appData.icsActive) {
10333             if (appData.quietPlay &&
10334                 (gameMode == IcsPlayingWhite ||
10335                  gameMode == IcsPlayingBlack)) {
10336                 SendToICS(ics_prefix);
10337                 SendToICS("set shout 1\n");
10338             }
10339             nextGameMode = IcsIdle;
10340             ics_user_moved = FALSE;
10341             /* clean up premove.  It's ugly when the game has ended and the
10342              * premove highlights are still on the board.
10343              */
10344             if (gotPremove) {
10345               gotPremove = FALSE;
10346               ClearPremoveHighlights();
10347               DrawPosition(FALSE, boards[currentMove]);
10348             }
10349             if (whosays == GE_ICS) {
10350                 switch (result) {
10351                 case WhiteWins:
10352                     if (gameMode == IcsPlayingWhite)
10353                         PlayIcsWinSound();
10354                     else if(gameMode == IcsPlayingBlack)
10355                         PlayIcsLossSound();
10356                     break;
10357                 case BlackWins:
10358                     if (gameMode == IcsPlayingBlack)
10359                         PlayIcsWinSound();
10360                     else if(gameMode == IcsPlayingWhite)
10361                         PlayIcsLossSound();
10362                     break;
10363                 case GameIsDrawn:
10364                     PlayIcsDrawSound();
10365                     break;
10366                 default:
10367                     PlayIcsUnfinishedSound();
10368                 }
10369             }
10370         } else if (gameMode == EditGame ||
10371                    gameMode == PlayFromGameFile ||
10372                    gameMode == AnalyzeMode ||
10373                    gameMode == AnalyzeFile) {
10374             nextGameMode = gameMode;
10375         } else {
10376             nextGameMode = EndOfGame;
10377         }
10378         pausing = FALSE;
10379         ModeHighlight();
10380     } else {
10381         nextGameMode = gameMode;
10382     }
10383
10384     if (appData.noChessProgram) {
10385         gameMode = nextGameMode;
10386         ModeHighlight();
10387         endingGame = 0; /* [HGM] crash */
10388         return;
10389     }
10390
10391     if (first.reuse) {
10392         /* Put first chess program into idle state */
10393         if (first.pr != NoProc &&
10394             (gameMode == MachinePlaysWhite ||
10395              gameMode == MachinePlaysBlack ||
10396              gameMode == TwoMachinesPlay ||
10397              gameMode == IcsPlayingWhite ||
10398              gameMode == IcsPlayingBlack ||
10399              gameMode == BeginningOfGame)) {
10400             SendToProgram("force\n", &first);
10401             if (first.usePing) {
10402               char buf[MSG_SIZ];
10403               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10404               SendToProgram(buf, &first);
10405             }
10406         }
10407     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10408         /* Kill off first chess program */
10409         if (first.isr != NULL)
10410           RemoveInputSource(first.isr);
10411         first.isr = NULL;
10412
10413         if (first.pr != NoProc) {
10414             ExitAnalyzeMode();
10415             DoSleep( appData.delayBeforeQuit );
10416             SendToProgram("quit\n", &first);
10417             DoSleep( appData.delayAfterQuit );
10418             DestroyChildProcess(first.pr, first.useSigterm);
10419         }
10420         first.pr = NoProc;
10421     }
10422     if (second.reuse) {
10423         /* Put second chess program into idle state */
10424         if (second.pr != NoProc &&
10425             gameMode == TwoMachinesPlay) {
10426             SendToProgram("force\n", &second);
10427             if (second.usePing) {
10428               char buf[MSG_SIZ];
10429               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10430               SendToProgram(buf, &second);
10431             }
10432         }
10433     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10434         /* Kill off second chess program */
10435         if (second.isr != NULL)
10436           RemoveInputSource(second.isr);
10437         second.isr = NULL;
10438
10439         if (second.pr != NoProc) {
10440             DoSleep( appData.delayBeforeQuit );
10441             SendToProgram("quit\n", &second);
10442             DoSleep( appData.delayAfterQuit );
10443             DestroyChildProcess(second.pr, second.useSigterm);
10444         }
10445         second.pr = NoProc;
10446     }
10447
10448     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10449         char resChar = '=';
10450         switch (result) {
10451         case WhiteWins:
10452           resChar = '+';
10453           if (first.twoMachinesColor[0] == 'w') {
10454             first.matchWins++;
10455           } else {
10456             second.matchWins++;
10457           }
10458           break;
10459         case BlackWins:
10460           resChar = '-';
10461           if (first.twoMachinesColor[0] == 'b') {
10462             first.matchWins++;
10463           } else {
10464             second.matchWins++;
10465           }
10466           break;
10467         case GameUnfinished:
10468           resChar = ' ';
10469         default:
10470           break;
10471         }
10472
10473         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10474         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10475             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10476             ReserveGame(nextGame, resChar); // sets nextGame
10477             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10478             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10479         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10480
10481         if (nextGame <= appData.matchGames && !abortMatch) {
10482             gameMode = nextGameMode;
10483             matchGame = nextGame; // this will be overruled in tourney mode!
10484             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10485             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10486             endingGame = 0; /* [HGM] crash */
10487             return;
10488         } else {
10489             gameMode = nextGameMode;
10490             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10491                      first.tidy, second.tidy,
10492                      first.matchWins, second.matchWins,
10493                      appData.matchGames - (first.matchWins + second.matchWins));
10494             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10495             if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10496             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10497             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10498                 first.twoMachinesColor = "black\n";
10499                 second.twoMachinesColor = "white\n";
10500             } else {
10501                 first.twoMachinesColor = "white\n";
10502                 second.twoMachinesColor = "black\n";
10503             }
10504         }
10505     }
10506     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10507         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10508       ExitAnalyzeMode();
10509     gameMode = nextGameMode;
10510     ModeHighlight();
10511     endingGame = 0;  /* [HGM] crash */
10512     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10513         if(matchMode == TRUE) { // match through command line: exit with or without popup
10514             if(ranking) {
10515                 ToNrEvent(forwardMostMove);
10516                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10517                 else ExitEvent(0);
10518             } else DisplayFatalError(buf, 0, 0);
10519         } else { // match through menu; just stop, with or without popup
10520             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10521             ModeHighlight();
10522             if(ranking){
10523                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10524             } else DisplayNote(buf);
10525       }
10526       if(ranking) free(ranking);
10527     }
10528 }
10529
10530 /* Assumes program was just initialized (initString sent).
10531    Leaves program in force mode. */
10532 void
10533 FeedMovesToProgram(cps, upto)
10534      ChessProgramState *cps;
10535      int upto;
10536 {
10537     int i;
10538
10539     if (appData.debugMode)
10540       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10541               startedFromSetupPosition ? "position and " : "",
10542               backwardMostMove, upto, cps->which);
10543     if(currentlyInitializedVariant != gameInfo.variant) {
10544       char buf[MSG_SIZ];
10545         // [HGM] variantswitch: make engine aware of new variant
10546         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10547                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10548         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10549         SendToProgram(buf, cps);
10550         currentlyInitializedVariant = gameInfo.variant;
10551     }
10552     SendToProgram("force\n", cps);
10553     if (startedFromSetupPosition) {
10554         SendBoard(cps, backwardMostMove);
10555     if (appData.debugMode) {
10556         fprintf(debugFP, "feedMoves\n");
10557     }
10558     }
10559     for (i = backwardMostMove; i < upto; i++) {
10560         SendMoveToProgram(i, cps);
10561     }
10562 }
10563
10564
10565 int
10566 ResurrectChessProgram()
10567 {
10568      /* The chess program may have exited.
10569         If so, restart it and feed it all the moves made so far. */
10570     static int doInit = 0;
10571
10572     if (appData.noChessProgram) return 1;
10573
10574     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10575         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10576         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10577         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10578     } else {
10579         if (first.pr != NoProc) return 1;
10580         StartChessProgram(&first);
10581     }
10582     InitChessProgram(&first, FALSE);
10583     FeedMovesToProgram(&first, currentMove);
10584
10585     if (!first.sendTime) {
10586         /* can't tell gnuchess what its clock should read,
10587            so we bow to its notion. */
10588         ResetClocks();
10589         timeRemaining[0][currentMove] = whiteTimeRemaining;
10590         timeRemaining[1][currentMove] = blackTimeRemaining;
10591     }
10592
10593     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10594                 appData.icsEngineAnalyze) && first.analysisSupport) {
10595       SendToProgram("analyze\n", &first);
10596       first.analyzing = TRUE;
10597     }
10598     return 1;
10599 }
10600
10601 /*
10602  * Button procedures
10603  */
10604 void
10605 Reset(redraw, init)
10606      int redraw, init;
10607 {
10608     int i;
10609
10610     if (appData.debugMode) {
10611         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10612                 redraw, init, gameMode);
10613     }
10614     CleanupTail(); // [HGM] vari: delete any stored variations
10615     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10616     pausing = pauseExamInvalid = FALSE;
10617     startedFromSetupPosition = blackPlaysFirst = FALSE;
10618     firstMove = TRUE;
10619     whiteFlag = blackFlag = FALSE;
10620     userOfferedDraw = FALSE;
10621     hintRequested = bookRequested = FALSE;
10622     first.maybeThinking = FALSE;
10623     second.maybeThinking = FALSE;
10624     first.bookSuspend = FALSE; // [HGM] book
10625     second.bookSuspend = FALSE;
10626     thinkOutput[0] = NULLCHAR;
10627     lastHint[0] = NULLCHAR;
10628     ClearGameInfo(&gameInfo);
10629     gameInfo.variant = StringToVariant(appData.variant);
10630     ics_user_moved = ics_clock_paused = FALSE;
10631     ics_getting_history = H_FALSE;
10632     ics_gamenum = -1;
10633     white_holding[0] = black_holding[0] = NULLCHAR;
10634     ClearProgramStats();
10635     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10636
10637     ResetFrontEnd();
10638     ClearHighlights();
10639     flipView = appData.flipView;
10640     ClearPremoveHighlights();
10641     gotPremove = FALSE;
10642     alarmSounded = FALSE;
10643
10644     GameEnds(EndOfFile, NULL, GE_PLAYER);
10645     if(appData.serverMovesName != NULL) {
10646         /* [HGM] prepare to make moves file for broadcasting */
10647         clock_t t = clock();
10648         if(serverMoves != NULL) fclose(serverMoves);
10649         serverMoves = fopen(appData.serverMovesName, "r");
10650         if(serverMoves != NULL) {
10651             fclose(serverMoves);
10652             /* delay 15 sec before overwriting, so all clients can see end */
10653             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10654         }
10655         serverMoves = fopen(appData.serverMovesName, "w");
10656     }
10657
10658     ExitAnalyzeMode();
10659     gameMode = BeginningOfGame;
10660     ModeHighlight();
10661     if(appData.icsActive) gameInfo.variant = VariantNormal;
10662     currentMove = forwardMostMove = backwardMostMove = 0;
10663     InitPosition(redraw);
10664     for (i = 0; i < MAX_MOVES; i++) {
10665         if (commentList[i] != NULL) {
10666             free(commentList[i]);
10667             commentList[i] = NULL;
10668         }
10669     }
10670     ResetClocks();
10671     timeRemaining[0][0] = whiteTimeRemaining;
10672     timeRemaining[1][0] = blackTimeRemaining;
10673
10674     if (first.pr == NoProc) {
10675         StartChessProgram(&first);
10676     }
10677     if (init) {
10678             InitChessProgram(&first, startedFromSetupPosition);
10679     }
10680     DisplayTitle("");
10681     DisplayMessage("", "");
10682     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10683     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10684 }
10685
10686 void
10687 AutoPlayGameLoop()
10688 {
10689     for (;;) {
10690         if (!AutoPlayOneMove())
10691           return;
10692         if (matchMode || appData.timeDelay == 0)
10693           continue;
10694         if (appData.timeDelay < 0)
10695           return;
10696         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10697         break;
10698     }
10699 }
10700
10701
10702 int
10703 AutoPlayOneMove()
10704 {
10705     int fromX, fromY, toX, toY;
10706
10707     if (appData.debugMode) {
10708       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10709     }
10710
10711     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10712       return FALSE;
10713
10714     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10715       pvInfoList[currentMove].depth = programStats.depth;
10716       pvInfoList[currentMove].score = programStats.score;
10717       pvInfoList[currentMove].time  = 0;
10718       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10719     }
10720
10721     if (currentMove >= forwardMostMove) {
10722       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10723 //      gameMode = EndOfGame;
10724 //      ModeHighlight();
10725
10726       /* [AS] Clear current move marker at the end of a game */
10727       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10728
10729       return FALSE;
10730     }
10731
10732     toX = moveList[currentMove][2] - AAA;
10733     toY = moveList[currentMove][3] - ONE;
10734
10735     if (moveList[currentMove][1] == '@') {
10736         if (appData.highlightLastMove) {
10737             SetHighlights(-1, -1, toX, toY);
10738         }
10739     } else {
10740         fromX = moveList[currentMove][0] - AAA;
10741         fromY = moveList[currentMove][1] - ONE;
10742
10743         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10744
10745         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10746
10747         if (appData.highlightLastMove) {
10748             SetHighlights(fromX, fromY, toX, toY);
10749         }
10750     }
10751     DisplayMove(currentMove);
10752     SendMoveToProgram(currentMove++, &first);
10753     DisplayBothClocks();
10754     DrawPosition(FALSE, boards[currentMove]);
10755     // [HGM] PV info: always display, routine tests if empty
10756     DisplayComment(currentMove - 1, commentList[currentMove]);
10757     return TRUE;
10758 }
10759
10760
10761 int
10762 LoadGameOneMove(readAhead)
10763      ChessMove readAhead;
10764 {
10765     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10766     char promoChar = NULLCHAR;
10767     ChessMove moveType;
10768     char move[MSG_SIZ];
10769     char *p, *q;
10770
10771     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10772         gameMode != AnalyzeMode && gameMode != Training) {
10773         gameFileFP = NULL;
10774         return FALSE;
10775     }
10776
10777     yyboardindex = forwardMostMove;
10778     if (readAhead != EndOfFile) {
10779       moveType = readAhead;
10780     } else {
10781       if (gameFileFP == NULL)
10782           return FALSE;
10783       moveType = (ChessMove) Myylex();
10784     }
10785
10786     done = FALSE;
10787     switch (moveType) {
10788       case Comment:
10789         if (appData.debugMode)
10790           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10791         p = yy_text;
10792
10793         /* append the comment but don't display it */
10794         AppendComment(currentMove, p, FALSE);
10795         return TRUE;
10796
10797       case WhiteCapturesEnPassant:
10798       case BlackCapturesEnPassant:
10799       case WhitePromotion:
10800       case BlackPromotion:
10801       case WhiteNonPromotion:
10802       case BlackNonPromotion:
10803       case NormalMove:
10804       case WhiteKingSideCastle:
10805       case WhiteQueenSideCastle:
10806       case BlackKingSideCastle:
10807       case BlackQueenSideCastle:
10808       case WhiteKingSideCastleWild:
10809       case WhiteQueenSideCastleWild:
10810       case BlackKingSideCastleWild:
10811       case BlackQueenSideCastleWild:
10812       /* PUSH Fabien */
10813       case WhiteHSideCastleFR:
10814       case WhiteASideCastleFR:
10815       case BlackHSideCastleFR:
10816       case BlackASideCastleFR:
10817       /* POP Fabien */
10818         if (appData.debugMode)
10819           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10820         fromX = currentMoveString[0] - AAA;
10821         fromY = currentMoveString[1] - ONE;
10822         toX = currentMoveString[2] - AAA;
10823         toY = currentMoveString[3] - ONE;
10824         promoChar = currentMoveString[4];
10825         break;
10826
10827       case WhiteDrop:
10828       case BlackDrop:
10829         if (appData.debugMode)
10830           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10831         fromX = moveType == WhiteDrop ?
10832           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10833         (int) CharToPiece(ToLower(currentMoveString[0]));
10834         fromY = DROP_RANK;
10835         toX = currentMoveString[2] - AAA;
10836         toY = currentMoveString[3] - ONE;
10837         break;
10838
10839       case WhiteWins:
10840       case BlackWins:
10841       case GameIsDrawn:
10842       case GameUnfinished:
10843         if (appData.debugMode)
10844           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10845         p = strchr(yy_text, '{');
10846         if (p == NULL) p = strchr(yy_text, '(');
10847         if (p == NULL) {
10848             p = yy_text;
10849             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10850         } else {
10851             q = strchr(p, *p == '{' ? '}' : ')');
10852             if (q != NULL) *q = NULLCHAR;
10853             p++;
10854         }
10855         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10856         GameEnds(moveType, p, GE_FILE);
10857         done = TRUE;
10858         if (cmailMsgLoaded) {
10859             ClearHighlights();
10860             flipView = WhiteOnMove(currentMove);
10861             if (moveType == GameUnfinished) flipView = !flipView;
10862             if (appData.debugMode)
10863               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10864         }
10865         break;
10866
10867       case EndOfFile:
10868         if (appData.debugMode)
10869           fprintf(debugFP, "Parser hit end of file\n");
10870         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10871           case MT_NONE:
10872           case MT_CHECK:
10873             break;
10874           case MT_CHECKMATE:
10875           case MT_STAINMATE:
10876             if (WhiteOnMove(currentMove)) {
10877                 GameEnds(BlackWins, "Black mates", GE_FILE);
10878             } else {
10879                 GameEnds(WhiteWins, "White mates", GE_FILE);
10880             }
10881             break;
10882           case MT_STALEMATE:
10883             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10884             break;
10885         }
10886         done = TRUE;
10887         break;
10888
10889       case MoveNumberOne:
10890         if (lastLoadGameStart == GNUChessGame) {
10891             /* GNUChessGames have numbers, but they aren't move numbers */
10892             if (appData.debugMode)
10893               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10894                       yy_text, (int) moveType);
10895             return LoadGameOneMove(EndOfFile); /* tail recursion */
10896         }
10897         /* else fall thru */
10898
10899       case XBoardGame:
10900       case GNUChessGame:
10901       case PGNTag:
10902         /* Reached start of next game in file */
10903         if (appData.debugMode)
10904           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10905         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10906           case MT_NONE:
10907           case MT_CHECK:
10908             break;
10909           case MT_CHECKMATE:
10910           case MT_STAINMATE:
10911             if (WhiteOnMove(currentMove)) {
10912                 GameEnds(BlackWins, "Black mates", GE_FILE);
10913             } else {
10914                 GameEnds(WhiteWins, "White mates", GE_FILE);
10915             }
10916             break;
10917           case MT_STALEMATE:
10918             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10919             break;
10920         }
10921         done = TRUE;
10922         break;
10923
10924       case PositionDiagram:     /* should not happen; ignore */
10925       case ElapsedTime:         /* ignore */
10926       case NAG:                 /* ignore */
10927         if (appData.debugMode)
10928           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10929                   yy_text, (int) moveType);
10930         return LoadGameOneMove(EndOfFile); /* tail recursion */
10931
10932       case IllegalMove:
10933         if (appData.testLegality) {
10934             if (appData.debugMode)
10935               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10936             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10937                     (forwardMostMove / 2) + 1,
10938                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10939             DisplayError(move, 0);
10940             done = TRUE;
10941         } else {
10942             if (appData.debugMode)
10943               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10944                       yy_text, currentMoveString);
10945             fromX = currentMoveString[0] - AAA;
10946             fromY = currentMoveString[1] - ONE;
10947             toX = currentMoveString[2] - AAA;
10948             toY = currentMoveString[3] - ONE;
10949             promoChar = currentMoveString[4];
10950         }
10951         break;
10952
10953       case AmbiguousMove:
10954         if (appData.debugMode)
10955           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10956         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10957                 (forwardMostMove / 2) + 1,
10958                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10959         DisplayError(move, 0);
10960         done = TRUE;
10961         break;
10962
10963       default:
10964       case ImpossibleMove:
10965         if (appData.debugMode)
10966           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10967         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10968                 (forwardMostMove / 2) + 1,
10969                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10970         DisplayError(move, 0);
10971         done = TRUE;
10972         break;
10973     }
10974
10975     if (done) {
10976         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10977             DrawPosition(FALSE, boards[currentMove]);
10978             DisplayBothClocks();
10979             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10980               DisplayComment(currentMove - 1, commentList[currentMove]);
10981         }
10982         (void) StopLoadGameTimer();
10983         gameFileFP = NULL;
10984         cmailOldMove = forwardMostMove;
10985         return FALSE;
10986     } else {
10987         /* currentMoveString is set as a side-effect of yylex */
10988
10989         thinkOutput[0] = NULLCHAR;
10990         MakeMove(fromX, fromY, toX, toY, promoChar);
10991         currentMove = forwardMostMove;
10992         return TRUE;
10993     }
10994 }
10995
10996 /* Load the nth game from the given file */
10997 int
10998 LoadGameFromFile(filename, n, title, useList)
10999      char *filename;
11000      int n;
11001      char *title;
11002      /*Boolean*/ int useList;
11003 {
11004     FILE *f;
11005     char buf[MSG_SIZ];
11006
11007     if (strcmp(filename, "-") == 0) {
11008         f = stdin;
11009         title = "stdin";
11010     } else {
11011         f = fopen(filename, "rb");
11012         if (f == NULL) {
11013           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11014             DisplayError(buf, errno);
11015             return FALSE;
11016         }
11017     }
11018     if (fseek(f, 0, 0) == -1) {
11019         /* f is not seekable; probably a pipe */
11020         useList = FALSE;
11021     }
11022     if (useList && n == 0) {
11023         int error = GameListBuild(f);
11024         if (error) {
11025             DisplayError(_("Cannot build game list"), error);
11026         } else if (!ListEmpty(&gameList) &&
11027                    ((ListGame *) gameList.tailPred)->number > 1) {
11028             GameListPopUp(f, title);
11029             return TRUE;
11030         }
11031         GameListDestroy();
11032         n = 1;
11033     }
11034     if (n == 0) n = 1;
11035     return LoadGame(f, n, title, FALSE);
11036 }
11037
11038
11039 void
11040 MakeRegisteredMove()
11041 {
11042     int fromX, fromY, toX, toY;
11043     char promoChar;
11044     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11045         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11046           case CMAIL_MOVE:
11047           case CMAIL_DRAW:
11048             if (appData.debugMode)
11049               fprintf(debugFP, "Restoring %s for game %d\n",
11050                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11051
11052             thinkOutput[0] = NULLCHAR;
11053             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11054             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11055             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11056             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11057             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11058             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11059             MakeMove(fromX, fromY, toX, toY, promoChar);
11060             ShowMove(fromX, fromY, toX, toY);
11061
11062             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11063               case MT_NONE:
11064               case MT_CHECK:
11065                 break;
11066
11067               case MT_CHECKMATE:
11068               case MT_STAINMATE:
11069                 if (WhiteOnMove(currentMove)) {
11070                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11071                 } else {
11072                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11073                 }
11074                 break;
11075
11076               case MT_STALEMATE:
11077                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11078                 break;
11079             }
11080
11081             break;
11082
11083           case CMAIL_RESIGN:
11084             if (WhiteOnMove(currentMove)) {
11085                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11086             } else {
11087                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11088             }
11089             break;
11090
11091           case CMAIL_ACCEPT:
11092             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11093             break;
11094
11095           default:
11096             break;
11097         }
11098     }
11099
11100     return;
11101 }
11102
11103 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11104 int
11105 CmailLoadGame(f, gameNumber, title, useList)
11106      FILE *f;
11107      int gameNumber;
11108      char *title;
11109      int useList;
11110 {
11111     int retVal;
11112
11113     if (gameNumber > nCmailGames) {
11114         DisplayError(_("No more games in this message"), 0);
11115         return FALSE;
11116     }
11117     if (f == lastLoadGameFP) {
11118         int offset = gameNumber - lastLoadGameNumber;
11119         if (offset == 0) {
11120             cmailMsg[0] = NULLCHAR;
11121             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11122                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11123                 nCmailMovesRegistered--;
11124             }
11125             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11126             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11127                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11128             }
11129         } else {
11130             if (! RegisterMove()) return FALSE;
11131         }
11132     }
11133
11134     retVal = LoadGame(f, gameNumber, title, useList);
11135
11136     /* Make move registered during previous look at this game, if any */
11137     MakeRegisteredMove();
11138
11139     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11140         commentList[currentMove]
11141           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11142         DisplayComment(currentMove - 1, commentList[currentMove]);
11143     }
11144
11145     return retVal;
11146 }
11147
11148 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11149 int
11150 ReloadGame(offset)
11151      int offset;
11152 {
11153     int gameNumber = lastLoadGameNumber + offset;
11154     if (lastLoadGameFP == NULL) {
11155         DisplayError(_("No game has been loaded yet"), 0);
11156         return FALSE;
11157     }
11158     if (gameNumber <= 0) {
11159         DisplayError(_("Can't back up any further"), 0);
11160         return FALSE;
11161     }
11162     if (cmailMsgLoaded) {
11163         return CmailLoadGame(lastLoadGameFP, gameNumber,
11164                              lastLoadGameTitle, lastLoadGameUseList);
11165     } else {
11166         return LoadGame(lastLoadGameFP, gameNumber,
11167                         lastLoadGameTitle, lastLoadGameUseList);
11168     }
11169 }
11170
11171 int keys[EmptySquare+1];
11172
11173 int
11174 PositionMatches(Board b1, Board b2)
11175 {
11176     int r, f, sum=0;
11177     switch(appData.searchMode) {
11178         case 1: return CompareWithRights(b1, b2);
11179         case 2:
11180             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11181                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11182             }
11183             return TRUE;
11184         case 3:
11185             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11186               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11187                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11188             }
11189             return sum==0;
11190         case 4:
11191             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11192                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11193             }
11194             return sum==0;
11195     }
11196     return TRUE;
11197 }
11198
11199 #define Q_PROMO  4
11200 #define Q_EP     3
11201 #define Q_BCASTL 2
11202 #define Q_WCASTL 1
11203
11204 int pieceList[256], quickBoard[256];
11205 ChessSquare pieceType[256] = { EmptySquare };
11206 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11207 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11208 int soughtTotal, turn;
11209 Boolean epOK, flipSearch;
11210
11211 typedef struct {
11212     unsigned char piece, to;
11213 } Move;
11214
11215 #define DSIZE (250000)
11216
11217 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11218 Move *moveDatabase = initialSpace;
11219 unsigned int movePtr, dataSize = DSIZE;
11220
11221 int MakePieceList(Board board, int *counts)
11222 {
11223     int r, f, n=Q_PROMO, total=0;
11224     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11225     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11226         int sq = f + (r<<4);
11227         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11228             quickBoard[sq] = ++n;
11229             pieceList[n] = sq;
11230             pieceType[n] = board[r][f];
11231             counts[board[r][f]]++;
11232             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11233             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11234             total++;
11235         }
11236     }
11237     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11238     return total;
11239 }
11240
11241 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11242 {
11243     int sq = fromX + (fromY<<4);
11244     int piece = quickBoard[sq];
11245     quickBoard[sq] = 0;
11246     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11247     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11248         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11249         moveDatabase[movePtr++].piece = Q_WCASTL;
11250         quickBoard[sq] = piece;
11251         piece = quickBoard[from]; quickBoard[from] = 0;
11252         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11253     } else
11254     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11255         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11256         moveDatabase[movePtr++].piece = Q_BCASTL;
11257         quickBoard[sq] = piece;
11258         piece = quickBoard[from]; quickBoard[from] = 0;
11259         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11260     } else
11261     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11262         quickBoard[(fromY<<4)+toX] = 0;
11263         moveDatabase[movePtr].piece = Q_EP;
11264         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11265         moveDatabase[movePtr].to = sq;
11266     } else
11267     if(promoPiece != pieceType[piece]) {
11268         moveDatabase[movePtr++].piece = Q_PROMO;
11269         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11270     }
11271     moveDatabase[movePtr].piece = piece;
11272     quickBoard[sq] = piece;
11273     movePtr++;
11274 }
11275
11276 int PackGame(Board board)
11277 {
11278     Move *newSpace = NULL;
11279     moveDatabase[movePtr].piece = 0; // terminate previous game
11280     if(movePtr > dataSize) {
11281         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11282         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11283         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11284         if(newSpace) {
11285             int i;
11286             Move *p = moveDatabase, *q = newSpace;
11287             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11288             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11289             moveDatabase = newSpace;
11290         } else { // calloc failed, we must be out of memory. Too bad...
11291             dataSize = 0; // prevent calloc events for all subsequent games
11292             return 0;     // and signal this one isn't cached
11293         }
11294     }
11295     movePtr++;
11296     MakePieceList(board, counts);
11297     return movePtr;
11298 }
11299
11300 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11301 {   // compare according to search mode
11302     int r, f;
11303     switch(appData.searchMode)
11304     {
11305       case 1: // exact position match
11306         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11307         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11308             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11309         }
11310         break;
11311       case 2: // can have extra material on empty squares
11312         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11313             if(board[r][f] == EmptySquare) continue;
11314             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11315         }
11316         break;
11317       case 3: // material with exact Pawn structure
11318         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11319             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11320             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11321         } // fall through to material comparison
11322       case 4: // exact material
11323         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11324         break;
11325       case 6: // material range with given imbalance
11326         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11327         // fall through to range comparison
11328       case 5: // material range
11329         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11330     }
11331     return TRUE;
11332 }
11333
11334 int QuickScan(Board board, Move *move)
11335 {   // reconstruct game,and compare all positions in it
11336     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11337     do {
11338         int piece = move->piece;
11339         int to = move->to, from = pieceList[piece];
11340         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11341           if(!piece) return -1;
11342           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11343             piece = (++move)->piece;
11344             from = pieceList[piece];
11345             counts[pieceType[piece]]--;
11346             pieceType[piece] = (ChessSquare) move->to;
11347             counts[move->to]++;
11348           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11349             counts[pieceType[quickBoard[to]]]--;
11350             quickBoard[to] = 0; total--;
11351             move++;
11352             continue;
11353           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11354             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11355             from  = pieceList[piece]; // so this must be King
11356             quickBoard[from] = 0;
11357             quickBoard[to] = piece;
11358             pieceList[piece] = to;
11359             move++;
11360             continue;
11361           }
11362         }
11363         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11364         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11365         quickBoard[from] = 0;
11366         quickBoard[to] = piece;
11367         pieceList[piece] = to;
11368         cnt++; turn ^= 3;
11369         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11370            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11371            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11372                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11373           ) {
11374             static int lastCounts[EmptySquare+1];
11375             int i;
11376             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11377             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11378         } else stretch = 0;
11379         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11380         move++;
11381     } while(1);
11382 }
11383
11384 void InitSearch()
11385 {
11386     int r, f;
11387     flipSearch = FALSE;
11388     CopyBoard(soughtBoard, boards[currentMove]);
11389     soughtTotal = MakePieceList(soughtBoard, maxSought);
11390     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11391     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11392     CopyBoard(reverseBoard, boards[currentMove]);
11393     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11394         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11395         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11396         reverseBoard[r][f] = piece;
11397     }
11398     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11399     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11400     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11401                  || (boards[currentMove][CASTLING][2] == NoRights || 
11402                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11403                  && (boards[currentMove][CASTLING][5] == NoRights || 
11404                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11405       ) {
11406         flipSearch = TRUE;
11407         CopyBoard(flipBoard, soughtBoard);
11408         CopyBoard(rotateBoard, reverseBoard);
11409         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11410             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11411             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11412         }
11413     }
11414     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11415     if(appData.searchMode >= 5) {
11416         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11417         MakePieceList(soughtBoard, minSought);
11418         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11419     }
11420     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11421         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11422 }
11423
11424 GameInfo dummyInfo;
11425
11426 int GameContainsPosition(FILE *f, ListGame *lg)
11427 {
11428     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11429     int fromX, fromY, toX, toY;
11430     char promoChar;
11431     static int initDone=FALSE;
11432
11433     // weed out games based on numerical tag comparison
11434     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11435     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11436     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11437     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11438     if(!initDone) {
11439         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11440         initDone = TRUE;
11441     }
11442     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11443     else CopyBoard(boards[scratch], initialPosition); // default start position
11444     if(lg->moves) {
11445         turn = btm + 1;
11446         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11447         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11448     }
11449     if(btm) plyNr++;
11450     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11451     fseek(f, lg->offset, 0);
11452     yynewfile(f);
11453     while(1) {
11454         yyboardindex = scratch;
11455         quickFlag = plyNr+1;
11456         next = Myylex();
11457         quickFlag = 0;
11458         switch(next) {
11459             case PGNTag:
11460                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11461             default:
11462                 continue;
11463
11464             case XBoardGame:
11465             case GNUChessGame:
11466                 if(plyNr) return -1; // after we have seen moves, this is for new game
11467               continue;
11468
11469             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11470             case ImpossibleMove:
11471             case WhiteWins: // game ends here with these four
11472             case BlackWins:
11473             case GameIsDrawn:
11474             case GameUnfinished:
11475                 return -1;
11476
11477             case IllegalMove:
11478                 if(appData.testLegality) return -1;
11479             case WhiteCapturesEnPassant:
11480             case BlackCapturesEnPassant:
11481             case WhitePromotion:
11482             case BlackPromotion:
11483             case WhiteNonPromotion:
11484             case BlackNonPromotion:
11485             case NormalMove:
11486             case WhiteKingSideCastle:
11487             case WhiteQueenSideCastle:
11488             case BlackKingSideCastle:
11489             case BlackQueenSideCastle:
11490             case WhiteKingSideCastleWild:
11491             case WhiteQueenSideCastleWild:
11492             case BlackKingSideCastleWild:
11493             case BlackQueenSideCastleWild:
11494             case WhiteHSideCastleFR:
11495             case WhiteASideCastleFR:
11496             case BlackHSideCastleFR:
11497             case BlackASideCastleFR:
11498                 fromX = currentMoveString[0] - AAA;
11499                 fromY = currentMoveString[1] - ONE;
11500                 toX = currentMoveString[2] - AAA;
11501                 toY = currentMoveString[3] - ONE;
11502                 promoChar = currentMoveString[4];
11503                 break;
11504             case WhiteDrop:
11505             case BlackDrop:
11506                 fromX = next == WhiteDrop ?
11507                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11508                   (int) CharToPiece(ToLower(currentMoveString[0]));
11509                 fromY = DROP_RANK;
11510                 toX = currentMoveString[2] - AAA;
11511                 toY = currentMoveString[3] - ONE;
11512                 promoChar = 0;
11513                 break;
11514         }
11515         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11516         plyNr++;
11517         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11518         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11519         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11520         if(appData.findMirror) {
11521             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11522             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11523         }
11524     }
11525 }
11526
11527 /* Load the nth game from open file f */
11528 int
11529 LoadGame(f, gameNumber, title, useList)
11530      FILE *f;
11531      int gameNumber;
11532      char *title;
11533      int useList;
11534 {
11535     ChessMove cm;
11536     char buf[MSG_SIZ];
11537     int gn = gameNumber;
11538     ListGame *lg = NULL;
11539     int numPGNTags = 0;
11540     int err, pos = -1;
11541     GameMode oldGameMode;
11542     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11543
11544     if (appData.debugMode)
11545         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11546
11547     if (gameMode == Training )
11548         SetTrainingModeOff();
11549
11550     oldGameMode = gameMode;
11551     if (gameMode != BeginningOfGame) {
11552       Reset(FALSE, TRUE);
11553     }
11554
11555     gameFileFP = f;
11556     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11557         fclose(lastLoadGameFP);
11558     }
11559
11560     if (useList) {
11561         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11562
11563         if (lg) {
11564             fseek(f, lg->offset, 0);
11565             GameListHighlight(gameNumber);
11566             pos = lg->position;
11567             gn = 1;
11568         }
11569         else {
11570             DisplayError(_("Game number out of range"), 0);
11571             return FALSE;
11572         }
11573     } else {
11574         GameListDestroy();
11575         if (fseek(f, 0, 0) == -1) {
11576             if (f == lastLoadGameFP ?
11577                 gameNumber == lastLoadGameNumber + 1 :
11578                 gameNumber == 1) {
11579                 gn = 1;
11580             } else {
11581                 DisplayError(_("Can't seek on game file"), 0);
11582                 return FALSE;
11583             }
11584         }
11585     }
11586     lastLoadGameFP = f;
11587     lastLoadGameNumber = gameNumber;
11588     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11589     lastLoadGameUseList = useList;
11590
11591     yynewfile(f);
11592
11593     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11594       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11595                 lg->gameInfo.black);
11596             DisplayTitle(buf);
11597     } else if (*title != NULLCHAR) {
11598         if (gameNumber > 1) {
11599           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11600             DisplayTitle(buf);
11601         } else {
11602             DisplayTitle(title);
11603         }
11604     }
11605
11606     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11607         gameMode = PlayFromGameFile;
11608         ModeHighlight();
11609     }
11610
11611     currentMove = forwardMostMove = backwardMostMove = 0;
11612     CopyBoard(boards[0], initialPosition);
11613     StopClocks();
11614
11615     /*
11616      * Skip the first gn-1 games in the file.
11617      * Also skip over anything that precedes an identifiable
11618      * start of game marker, to avoid being confused by
11619      * garbage at the start of the file.  Currently
11620      * recognized start of game markers are the move number "1",
11621      * the pattern "gnuchess .* game", the pattern
11622      * "^[#;%] [^ ]* game file", and a PGN tag block.
11623      * A game that starts with one of the latter two patterns
11624      * will also have a move number 1, possibly
11625      * following a position diagram.
11626      * 5-4-02: Let's try being more lenient and allowing a game to
11627      * start with an unnumbered move.  Does that break anything?
11628      */
11629     cm = lastLoadGameStart = EndOfFile;
11630     while (gn > 0) {
11631         yyboardindex = forwardMostMove;
11632         cm = (ChessMove) Myylex();
11633         switch (cm) {
11634           case EndOfFile:
11635             if (cmailMsgLoaded) {
11636                 nCmailGames = CMAIL_MAX_GAMES - gn;
11637             } else {
11638                 Reset(TRUE, TRUE);
11639                 DisplayError(_("Game not found in file"), 0);
11640             }
11641             return FALSE;
11642
11643           case GNUChessGame:
11644           case XBoardGame:
11645             gn--;
11646             lastLoadGameStart = cm;
11647             break;
11648
11649           case MoveNumberOne:
11650             switch (lastLoadGameStart) {
11651               case GNUChessGame:
11652               case XBoardGame:
11653               case PGNTag:
11654                 break;
11655               case MoveNumberOne:
11656               case EndOfFile:
11657                 gn--;           /* count this game */
11658                 lastLoadGameStart = cm;
11659                 break;
11660               default:
11661                 /* impossible */
11662                 break;
11663             }
11664             break;
11665
11666           case PGNTag:
11667             switch (lastLoadGameStart) {
11668               case GNUChessGame:
11669               case PGNTag:
11670               case MoveNumberOne:
11671               case EndOfFile:
11672                 gn--;           /* count this game */
11673                 lastLoadGameStart = cm;
11674                 break;
11675               case XBoardGame:
11676                 lastLoadGameStart = cm; /* game counted already */
11677                 break;
11678               default:
11679                 /* impossible */
11680                 break;
11681             }
11682             if (gn > 0) {
11683                 do {
11684                     yyboardindex = forwardMostMove;
11685                     cm = (ChessMove) Myylex();
11686                 } while (cm == PGNTag || cm == Comment);
11687             }
11688             break;
11689
11690           case WhiteWins:
11691           case BlackWins:
11692           case GameIsDrawn:
11693             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11694                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11695                     != CMAIL_OLD_RESULT) {
11696                     nCmailResults ++ ;
11697                     cmailResult[  CMAIL_MAX_GAMES
11698                                 - gn - 1] = CMAIL_OLD_RESULT;
11699                 }
11700             }
11701             break;
11702
11703           case NormalMove:
11704             /* Only a NormalMove can be at the start of a game
11705              * without a position diagram. */
11706             if (lastLoadGameStart == EndOfFile ) {
11707               gn--;
11708               lastLoadGameStart = MoveNumberOne;
11709             }
11710             break;
11711
11712           default:
11713             break;
11714         }
11715     }
11716
11717     if (appData.debugMode)
11718       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11719
11720     if (cm == XBoardGame) {
11721         /* Skip any header junk before position diagram and/or move 1 */
11722         for (;;) {
11723             yyboardindex = forwardMostMove;
11724             cm = (ChessMove) Myylex();
11725
11726             if (cm == EndOfFile ||
11727                 cm == GNUChessGame || cm == XBoardGame) {
11728                 /* Empty game; pretend end-of-file and handle later */
11729                 cm = EndOfFile;
11730                 break;
11731             }
11732
11733             if (cm == MoveNumberOne || cm == PositionDiagram ||
11734                 cm == PGNTag || cm == Comment)
11735               break;
11736         }
11737     } else if (cm == GNUChessGame) {
11738         if (gameInfo.event != NULL) {
11739             free(gameInfo.event);
11740         }
11741         gameInfo.event = StrSave(yy_text);
11742     }
11743
11744     startedFromSetupPosition = FALSE;
11745     while (cm == PGNTag) {
11746         if (appData.debugMode)
11747           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11748         err = ParsePGNTag(yy_text, &gameInfo);
11749         if (!err) numPGNTags++;
11750
11751         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11752         if(gameInfo.variant != oldVariant) {
11753             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11754             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11755             InitPosition(TRUE);
11756             oldVariant = gameInfo.variant;
11757             if (appData.debugMode)
11758               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11759         }
11760
11761
11762         if (gameInfo.fen != NULL) {
11763           Board initial_position;
11764           startedFromSetupPosition = TRUE;
11765           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11766             Reset(TRUE, TRUE);
11767             DisplayError(_("Bad FEN position in file"), 0);
11768             return FALSE;
11769           }
11770           CopyBoard(boards[0], initial_position);
11771           if (blackPlaysFirst) {
11772             currentMove = forwardMostMove = backwardMostMove = 1;
11773             CopyBoard(boards[1], initial_position);
11774             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11775             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11776             timeRemaining[0][1] = whiteTimeRemaining;
11777             timeRemaining[1][1] = blackTimeRemaining;
11778             if (commentList[0] != NULL) {
11779               commentList[1] = commentList[0];
11780               commentList[0] = NULL;
11781             }
11782           } else {
11783             currentMove = forwardMostMove = backwardMostMove = 0;
11784           }
11785           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11786           {   int i;
11787               initialRulePlies = FENrulePlies;
11788               for( i=0; i< nrCastlingRights; i++ )
11789                   initialRights[i] = initial_position[CASTLING][i];
11790           }
11791           yyboardindex = forwardMostMove;
11792           free(gameInfo.fen);
11793           gameInfo.fen = NULL;
11794         }
11795
11796         yyboardindex = forwardMostMove;
11797         cm = (ChessMove) Myylex();
11798
11799         /* Handle comments interspersed among the tags */
11800         while (cm == Comment) {
11801             char *p;
11802             if (appData.debugMode)
11803               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11804             p = yy_text;
11805             AppendComment(currentMove, p, FALSE);
11806             yyboardindex = forwardMostMove;
11807             cm = (ChessMove) Myylex();
11808         }
11809     }
11810
11811     /* don't rely on existence of Event tag since if game was
11812      * pasted from clipboard the Event tag may not exist
11813      */
11814     if (numPGNTags > 0){
11815         char *tags;
11816         if (gameInfo.variant == VariantNormal) {
11817           VariantClass v = StringToVariant(gameInfo.event);
11818           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11819           if(v < VariantShogi) gameInfo.variant = v;
11820         }
11821         if (!matchMode) {
11822           if( appData.autoDisplayTags ) {
11823             tags = PGNTags(&gameInfo);
11824             TagsPopUp(tags, CmailMsg());
11825             free(tags);
11826           }
11827         }
11828     } else {
11829         /* Make something up, but don't display it now */
11830         SetGameInfo();
11831         TagsPopDown();
11832     }
11833
11834     if (cm == PositionDiagram) {
11835         int i, j;
11836         char *p;
11837         Board initial_position;
11838
11839         if (appData.debugMode)
11840           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11841
11842         if (!startedFromSetupPosition) {
11843             p = yy_text;
11844             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11845               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11846                 switch (*p) {
11847                   case '{':
11848                   case '[':
11849                   case '-':
11850                   case ' ':
11851                   case '\t':
11852                   case '\n':
11853                   case '\r':
11854                     break;
11855                   default:
11856                     initial_position[i][j++] = CharToPiece(*p);
11857                     break;
11858                 }
11859             while (*p == ' ' || *p == '\t' ||
11860                    *p == '\n' || *p == '\r') p++;
11861
11862             if (strncmp(p, "black", strlen("black"))==0)
11863               blackPlaysFirst = TRUE;
11864             else
11865               blackPlaysFirst = FALSE;
11866             startedFromSetupPosition = TRUE;
11867
11868             CopyBoard(boards[0], initial_position);
11869             if (blackPlaysFirst) {
11870                 currentMove = forwardMostMove = backwardMostMove = 1;
11871                 CopyBoard(boards[1], initial_position);
11872                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11873                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11874                 timeRemaining[0][1] = whiteTimeRemaining;
11875                 timeRemaining[1][1] = blackTimeRemaining;
11876                 if (commentList[0] != NULL) {
11877                     commentList[1] = commentList[0];
11878                     commentList[0] = NULL;
11879                 }
11880             } else {
11881                 currentMove = forwardMostMove = backwardMostMove = 0;
11882             }
11883         }
11884         yyboardindex = forwardMostMove;
11885         cm = (ChessMove) Myylex();
11886     }
11887
11888     if (first.pr == NoProc) {
11889         StartChessProgram(&first);
11890     }
11891     InitChessProgram(&first, FALSE);
11892     SendToProgram("force\n", &first);
11893     if (startedFromSetupPosition) {
11894         SendBoard(&first, forwardMostMove);
11895     if (appData.debugMode) {
11896         fprintf(debugFP, "Load Game\n");
11897     }
11898         DisplayBothClocks();
11899     }
11900
11901     /* [HGM] server: flag to write setup moves in broadcast file as one */
11902     loadFlag = appData.suppressLoadMoves;
11903
11904     while (cm == Comment) {
11905         char *p;
11906         if (appData.debugMode)
11907           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11908         p = yy_text;
11909         AppendComment(currentMove, p, FALSE);
11910         yyboardindex = forwardMostMove;
11911         cm = (ChessMove) Myylex();
11912     }
11913
11914     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11915         cm == WhiteWins || cm == BlackWins ||
11916         cm == GameIsDrawn || cm == GameUnfinished) {
11917         DisplayMessage("", _("No moves in game"));
11918         if (cmailMsgLoaded) {
11919             if (appData.debugMode)
11920               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11921             ClearHighlights();
11922             flipView = FALSE;
11923         }
11924         DrawPosition(FALSE, boards[currentMove]);
11925         DisplayBothClocks();
11926         gameMode = EditGame;
11927         ModeHighlight();
11928         gameFileFP = NULL;
11929         cmailOldMove = 0;
11930         return TRUE;
11931     }
11932
11933     // [HGM] PV info: routine tests if comment empty
11934     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11935         DisplayComment(currentMove - 1, commentList[currentMove]);
11936     }
11937     if (!matchMode && appData.timeDelay != 0)
11938       DrawPosition(FALSE, boards[currentMove]);
11939
11940     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11941       programStats.ok_to_send = 1;
11942     }
11943
11944     /* if the first token after the PGN tags is a move
11945      * and not move number 1, retrieve it from the parser
11946      */
11947     if (cm != MoveNumberOne)
11948         LoadGameOneMove(cm);
11949
11950     /* load the remaining moves from the file */
11951     while (LoadGameOneMove(EndOfFile)) {
11952       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11953       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11954     }
11955
11956     /* rewind to the start of the game */
11957     currentMove = backwardMostMove;
11958
11959     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11960
11961     if (oldGameMode == AnalyzeFile ||
11962         oldGameMode == AnalyzeMode) {
11963       AnalyzeFileEvent();
11964     }
11965
11966     if (!matchMode && pos >= 0) {
11967         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11968     } else
11969     if (matchMode || appData.timeDelay == 0) {
11970       ToEndEvent();
11971     } else if (appData.timeDelay > 0) {
11972       AutoPlayGameLoop();
11973     }
11974
11975     if (appData.debugMode)
11976         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11977
11978     loadFlag = 0; /* [HGM] true game starts */
11979     return TRUE;
11980 }
11981
11982 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11983 int
11984 ReloadPosition(offset)
11985      int offset;
11986 {
11987     int positionNumber = lastLoadPositionNumber + offset;
11988     if (lastLoadPositionFP == NULL) {
11989         DisplayError(_("No position has been loaded yet"), 0);
11990         return FALSE;
11991     }
11992     if (positionNumber <= 0) {
11993         DisplayError(_("Can't back up any further"), 0);
11994         return FALSE;
11995     }
11996     return LoadPosition(lastLoadPositionFP, positionNumber,
11997                         lastLoadPositionTitle);
11998 }
11999
12000 /* Load the nth position from the given file */
12001 int
12002 LoadPositionFromFile(filename, n, title)
12003      char *filename;
12004      int n;
12005      char *title;
12006 {
12007     FILE *f;
12008     char buf[MSG_SIZ];
12009
12010     if (strcmp(filename, "-") == 0) {
12011         return LoadPosition(stdin, n, "stdin");
12012     } else {
12013         f = fopen(filename, "rb");
12014         if (f == NULL) {
12015             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12016             DisplayError(buf, errno);
12017             return FALSE;
12018         } else {
12019             return LoadPosition(f, n, title);
12020         }
12021     }
12022 }
12023
12024 /* Load the nth position from the given open file, and close it */
12025 int
12026 LoadPosition(f, positionNumber, title)
12027      FILE *f;
12028      int positionNumber;
12029      char *title;
12030 {
12031     char *p, line[MSG_SIZ];
12032     Board initial_position;
12033     int i, j, fenMode, pn;
12034
12035     if (gameMode == Training )
12036         SetTrainingModeOff();
12037
12038     if (gameMode != BeginningOfGame) {
12039         Reset(FALSE, TRUE);
12040     }
12041     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12042         fclose(lastLoadPositionFP);
12043     }
12044     if (positionNumber == 0) positionNumber = 1;
12045     lastLoadPositionFP = f;
12046     lastLoadPositionNumber = positionNumber;
12047     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12048     if (first.pr == NoProc && !appData.noChessProgram) {
12049       StartChessProgram(&first);
12050       InitChessProgram(&first, FALSE);
12051     }
12052     pn = positionNumber;
12053     if (positionNumber < 0) {
12054         /* Negative position number means to seek to that byte offset */
12055         if (fseek(f, -positionNumber, 0) == -1) {
12056             DisplayError(_("Can't seek on position file"), 0);
12057             return FALSE;
12058         };
12059         pn = 1;
12060     } else {
12061         if (fseek(f, 0, 0) == -1) {
12062             if (f == lastLoadPositionFP ?
12063                 positionNumber == lastLoadPositionNumber + 1 :
12064                 positionNumber == 1) {
12065                 pn = 1;
12066             } else {
12067                 DisplayError(_("Can't seek on position file"), 0);
12068                 return FALSE;
12069             }
12070         }
12071     }
12072     /* See if this file is FEN or old-style xboard */
12073     if (fgets(line, MSG_SIZ, f) == NULL) {
12074         DisplayError(_("Position not found in file"), 0);
12075         return FALSE;
12076     }
12077     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12078     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12079
12080     if (pn >= 2) {
12081         if (fenMode || line[0] == '#') pn--;
12082         while (pn > 0) {
12083             /* skip positions before number pn */
12084             if (fgets(line, MSG_SIZ, f) == NULL) {
12085                 Reset(TRUE, TRUE);
12086                 DisplayError(_("Position not found in file"), 0);
12087                 return FALSE;
12088             }
12089             if (fenMode || line[0] == '#') pn--;
12090         }
12091     }
12092
12093     if (fenMode) {
12094         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12095             DisplayError(_("Bad FEN position in file"), 0);
12096             return FALSE;
12097         }
12098     } else {
12099         (void) fgets(line, MSG_SIZ, f);
12100         (void) fgets(line, MSG_SIZ, f);
12101
12102         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12103             (void) fgets(line, MSG_SIZ, f);
12104             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12105                 if (*p == ' ')
12106                   continue;
12107                 initial_position[i][j++] = CharToPiece(*p);
12108             }
12109         }
12110
12111         blackPlaysFirst = FALSE;
12112         if (!feof(f)) {
12113             (void) fgets(line, MSG_SIZ, f);
12114             if (strncmp(line, "black", strlen("black"))==0)
12115               blackPlaysFirst = TRUE;
12116         }
12117     }
12118     startedFromSetupPosition = TRUE;
12119
12120     CopyBoard(boards[0], initial_position);
12121     if (blackPlaysFirst) {
12122         currentMove = forwardMostMove = backwardMostMove = 1;
12123         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12124         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12125         CopyBoard(boards[1], initial_position);
12126         DisplayMessage("", _("Black to play"));
12127     } else {
12128         currentMove = forwardMostMove = backwardMostMove = 0;
12129         DisplayMessage("", _("White to play"));
12130     }
12131     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12132     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12133         SendToProgram("force\n", &first);
12134         SendBoard(&first, forwardMostMove);
12135     }
12136     if (appData.debugMode) {
12137 int i, j;
12138   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12139   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12140         fprintf(debugFP, "Load Position\n");
12141     }
12142
12143     if (positionNumber > 1) {
12144       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12145         DisplayTitle(line);
12146     } else {
12147         DisplayTitle(title);
12148     }
12149     gameMode = EditGame;
12150     ModeHighlight();
12151     ResetClocks();
12152     timeRemaining[0][1] = whiteTimeRemaining;
12153     timeRemaining[1][1] = blackTimeRemaining;
12154     DrawPosition(FALSE, boards[currentMove]);
12155
12156     return TRUE;
12157 }
12158
12159
12160 void
12161 CopyPlayerNameIntoFileName(dest, src)
12162      char **dest, *src;
12163 {
12164     while (*src != NULLCHAR && *src != ',') {
12165         if (*src == ' ') {
12166             *(*dest)++ = '_';
12167             src++;
12168         } else {
12169             *(*dest)++ = *src++;
12170         }
12171     }
12172 }
12173
12174 char *DefaultFileName(ext)
12175      char *ext;
12176 {
12177     static char def[MSG_SIZ];
12178     char *p;
12179
12180     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12181         p = def;
12182         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12183         *p++ = '-';
12184         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12185         *p++ = '.';
12186         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12187     } else {
12188         def[0] = NULLCHAR;
12189     }
12190     return def;
12191 }
12192
12193 /* Save the current game to the given file */
12194 int
12195 SaveGameToFile(filename, append)
12196      char *filename;
12197      int append;
12198 {
12199     FILE *f;
12200     char buf[MSG_SIZ];
12201     int result, i, t,tot=0;
12202
12203     if (strcmp(filename, "-") == 0) {
12204         return SaveGame(stdout, 0, NULL);
12205     } else {
12206         for(i=0; i<10; i++) { // upto 10 tries
12207              f = fopen(filename, append ? "a" : "w");
12208              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12209              if(f || errno != 13) break;
12210              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12211              tot += t;
12212         }
12213         if (f == NULL) {
12214             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12215             DisplayError(buf, errno);
12216             return FALSE;
12217         } else {
12218             safeStrCpy(buf, lastMsg, MSG_SIZ);
12219             DisplayMessage(_("Waiting for access to save file"), "");
12220             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12221             DisplayMessage(_("Saving game"), "");
12222             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12223             result = SaveGame(f, 0, NULL);
12224             DisplayMessage(buf, "");
12225             return result;
12226         }
12227     }
12228 }
12229
12230 char *
12231 SavePart(str)
12232      char *str;
12233 {
12234     static char buf[MSG_SIZ];
12235     char *p;
12236
12237     p = strchr(str, ' ');
12238     if (p == NULL) return str;
12239     strncpy(buf, str, p - str);
12240     buf[p - str] = NULLCHAR;
12241     return buf;
12242 }
12243
12244 #define PGN_MAX_LINE 75
12245
12246 #define PGN_SIDE_WHITE  0
12247 #define PGN_SIDE_BLACK  1
12248
12249 /* [AS] */
12250 static int FindFirstMoveOutOfBook( int side )
12251 {
12252     int result = -1;
12253
12254     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12255         int index = backwardMostMove;
12256         int has_book_hit = 0;
12257
12258         if( (index % 2) != side ) {
12259             index++;
12260         }
12261
12262         while( index < forwardMostMove ) {
12263             /* Check to see if engine is in book */
12264             int depth = pvInfoList[index].depth;
12265             int score = pvInfoList[index].score;
12266             int in_book = 0;
12267
12268             if( depth <= 2 ) {
12269                 in_book = 1;
12270             }
12271             else if( score == 0 && depth == 63 ) {
12272                 in_book = 1; /* Zappa */
12273             }
12274             else if( score == 2 && depth == 99 ) {
12275                 in_book = 1; /* Abrok */
12276             }
12277
12278             has_book_hit += in_book;
12279
12280             if( ! in_book ) {
12281                 result = index;
12282
12283                 break;
12284             }
12285
12286             index += 2;
12287         }
12288     }
12289
12290     return result;
12291 }
12292
12293 /* [AS] */
12294 void GetOutOfBookInfo( char * buf )
12295 {
12296     int oob[2];
12297     int i;
12298     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12299
12300     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12301     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12302
12303     *buf = '\0';
12304
12305     if( oob[0] >= 0 || oob[1] >= 0 ) {
12306         for( i=0; i<2; i++ ) {
12307             int idx = oob[i];
12308
12309             if( idx >= 0 ) {
12310                 if( i > 0 && oob[0] >= 0 ) {
12311                     strcat( buf, "   " );
12312                 }
12313
12314                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12315                 sprintf( buf+strlen(buf), "%s%.2f",
12316                     pvInfoList[idx].score >= 0 ? "+" : "",
12317                     pvInfoList[idx].score / 100.0 );
12318             }
12319         }
12320     }
12321 }
12322
12323 /* Save game in PGN style and close the file */
12324 int
12325 SaveGamePGN(f)
12326      FILE *f;
12327 {
12328     int i, offset, linelen, newblock;
12329     time_t tm;
12330 //    char *movetext;
12331     char numtext[32];
12332     int movelen, numlen, blank;
12333     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12334
12335     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12336
12337     tm = time((time_t *) NULL);
12338
12339     PrintPGNTags(f, &gameInfo);
12340
12341     if (backwardMostMove > 0 || startedFromSetupPosition) {
12342         char *fen = PositionToFEN(backwardMostMove, NULL);
12343         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12344         fprintf(f, "\n{--------------\n");
12345         PrintPosition(f, backwardMostMove);
12346         fprintf(f, "--------------}\n");
12347         free(fen);
12348     }
12349     else {
12350         /* [AS] Out of book annotation */
12351         if( appData.saveOutOfBookInfo ) {
12352             char buf[64];
12353
12354             GetOutOfBookInfo( buf );
12355
12356             if( buf[0] != '\0' ) {
12357                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12358             }
12359         }
12360
12361         fprintf(f, "\n");
12362     }
12363
12364     i = backwardMostMove;
12365     linelen = 0;
12366     newblock = TRUE;
12367
12368     while (i < forwardMostMove) {
12369         /* Print comments preceding this move */
12370         if (commentList[i] != NULL) {
12371             if (linelen > 0) fprintf(f, "\n");
12372             fprintf(f, "%s", commentList[i]);
12373             linelen = 0;
12374             newblock = TRUE;
12375         }
12376
12377         /* Format move number */
12378         if ((i % 2) == 0)
12379           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12380         else
12381           if (newblock)
12382             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12383           else
12384             numtext[0] = NULLCHAR;
12385
12386         numlen = strlen(numtext);
12387         newblock = FALSE;
12388
12389         /* Print move number */
12390         blank = linelen > 0 && numlen > 0;
12391         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12392             fprintf(f, "\n");
12393             linelen = 0;
12394             blank = 0;
12395         }
12396         if (blank) {
12397             fprintf(f, " ");
12398             linelen++;
12399         }
12400         fprintf(f, "%s", numtext);
12401         linelen += numlen;
12402
12403         /* Get move */
12404         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12405         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12406
12407         /* Print move */
12408         blank = linelen > 0 && movelen > 0;
12409         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12410             fprintf(f, "\n");
12411             linelen = 0;
12412             blank = 0;
12413         }
12414         if (blank) {
12415             fprintf(f, " ");
12416             linelen++;
12417         }
12418         fprintf(f, "%s", move_buffer);
12419         linelen += movelen;
12420
12421         /* [AS] Add PV info if present */
12422         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12423             /* [HGM] add time */
12424             char buf[MSG_SIZ]; int seconds;
12425
12426             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12427
12428             if( seconds <= 0)
12429               buf[0] = 0;
12430             else
12431               if( seconds < 30 )
12432                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12433               else
12434                 {
12435                   seconds = (seconds + 4)/10; // round to full seconds
12436                   if( seconds < 60 )
12437                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12438                   else
12439                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12440                 }
12441
12442             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12443                       pvInfoList[i].score >= 0 ? "+" : "",
12444                       pvInfoList[i].score / 100.0,
12445                       pvInfoList[i].depth,
12446                       buf );
12447
12448             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12449
12450             /* Print score/depth */
12451             blank = linelen > 0 && movelen > 0;
12452             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12453                 fprintf(f, "\n");
12454                 linelen = 0;
12455                 blank = 0;
12456             }
12457             if (blank) {
12458                 fprintf(f, " ");
12459                 linelen++;
12460             }
12461             fprintf(f, "%s", move_buffer);
12462             linelen += movelen;
12463         }
12464
12465         i++;
12466     }
12467
12468     /* Start a new line */
12469     if (linelen > 0) fprintf(f, "\n");
12470
12471     /* Print comments after last move */
12472     if (commentList[i] != NULL) {
12473         fprintf(f, "%s\n", commentList[i]);
12474     }
12475
12476     /* Print result */
12477     if (gameInfo.resultDetails != NULL &&
12478         gameInfo.resultDetails[0] != NULLCHAR) {
12479         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12480                 PGNResult(gameInfo.result));
12481     } else {
12482         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12483     }
12484
12485     fclose(f);
12486     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12487     return TRUE;
12488 }
12489
12490 /* Save game in old style and close the file */
12491 int
12492 SaveGameOldStyle(f)
12493      FILE *f;
12494 {
12495     int i, offset;
12496     time_t tm;
12497
12498     tm = time((time_t *) NULL);
12499
12500     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12501     PrintOpponents(f);
12502
12503     if (backwardMostMove > 0 || startedFromSetupPosition) {
12504         fprintf(f, "\n[--------------\n");
12505         PrintPosition(f, backwardMostMove);
12506         fprintf(f, "--------------]\n");
12507     } else {
12508         fprintf(f, "\n");
12509     }
12510
12511     i = backwardMostMove;
12512     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12513
12514     while (i < forwardMostMove) {
12515         if (commentList[i] != NULL) {
12516             fprintf(f, "[%s]\n", commentList[i]);
12517         }
12518
12519         if ((i % 2) == 1) {
12520             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12521             i++;
12522         } else {
12523             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12524             i++;
12525             if (commentList[i] != NULL) {
12526                 fprintf(f, "\n");
12527                 continue;
12528             }
12529             if (i >= forwardMostMove) {
12530                 fprintf(f, "\n");
12531                 break;
12532             }
12533             fprintf(f, "%s\n", parseList[i]);
12534             i++;
12535         }
12536     }
12537
12538     if (commentList[i] != NULL) {
12539         fprintf(f, "[%s]\n", commentList[i]);
12540     }
12541
12542     /* This isn't really the old style, but it's close enough */
12543     if (gameInfo.resultDetails != NULL &&
12544         gameInfo.resultDetails[0] != NULLCHAR) {
12545         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12546                 gameInfo.resultDetails);
12547     } else {
12548         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12549     }
12550
12551     fclose(f);
12552     return TRUE;
12553 }
12554
12555 /* Save the current game to open file f and close the file */
12556 int
12557 SaveGame(f, dummy, dummy2)
12558      FILE *f;
12559      int dummy;
12560      char *dummy2;
12561 {
12562     if (gameMode == EditPosition) EditPositionDone(TRUE);
12563     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12564     if (appData.oldSaveStyle)
12565       return SaveGameOldStyle(f);
12566     else
12567       return SaveGamePGN(f);
12568 }
12569
12570 /* Save the current position to the given file */
12571 int
12572 SavePositionToFile(filename)
12573      char *filename;
12574 {
12575     FILE *f;
12576     char buf[MSG_SIZ];
12577
12578     if (strcmp(filename, "-") == 0) {
12579         return SavePosition(stdout, 0, NULL);
12580     } else {
12581         f = fopen(filename, "a");
12582         if (f == NULL) {
12583             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12584             DisplayError(buf, errno);
12585             return FALSE;
12586         } else {
12587             safeStrCpy(buf, lastMsg, MSG_SIZ);
12588             DisplayMessage(_("Waiting for access to save file"), "");
12589             flock(fileno(f), LOCK_EX); // [HGM] lock
12590             DisplayMessage(_("Saving position"), "");
12591             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12592             SavePosition(f, 0, NULL);
12593             DisplayMessage(buf, "");
12594             return TRUE;
12595         }
12596     }
12597 }
12598
12599 /* Save the current position to the given open file and close the file */
12600 int
12601 SavePosition(f, dummy, dummy2)
12602      FILE *f;
12603      int dummy;
12604      char *dummy2;
12605 {
12606     time_t tm;
12607     char *fen;
12608
12609     if (gameMode == EditPosition) EditPositionDone(TRUE);
12610     if (appData.oldSaveStyle) {
12611         tm = time((time_t *) NULL);
12612
12613         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12614         PrintOpponents(f);
12615         fprintf(f, "[--------------\n");
12616         PrintPosition(f, currentMove);
12617         fprintf(f, "--------------]\n");
12618     } else {
12619         fen = PositionToFEN(currentMove, NULL);
12620         fprintf(f, "%s\n", fen);
12621         free(fen);
12622     }
12623     fclose(f);
12624     return TRUE;
12625 }
12626
12627 void
12628 ReloadCmailMsgEvent(unregister)
12629      int unregister;
12630 {
12631 #if !WIN32
12632     static char *inFilename = NULL;
12633     static char *outFilename;
12634     int i;
12635     struct stat inbuf, outbuf;
12636     int status;
12637
12638     /* Any registered moves are unregistered if unregister is set, */
12639     /* i.e. invoked by the signal handler */
12640     if (unregister) {
12641         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12642             cmailMoveRegistered[i] = FALSE;
12643             if (cmailCommentList[i] != NULL) {
12644                 free(cmailCommentList[i]);
12645                 cmailCommentList[i] = NULL;
12646             }
12647         }
12648         nCmailMovesRegistered = 0;
12649     }
12650
12651     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12652         cmailResult[i] = CMAIL_NOT_RESULT;
12653     }
12654     nCmailResults = 0;
12655
12656     if (inFilename == NULL) {
12657         /* Because the filenames are static they only get malloced once  */
12658         /* and they never get freed                                      */
12659         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12660         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12661
12662         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12663         sprintf(outFilename, "%s.out", appData.cmailGameName);
12664     }
12665
12666     status = stat(outFilename, &outbuf);
12667     if (status < 0) {
12668         cmailMailedMove = FALSE;
12669     } else {
12670         status = stat(inFilename, &inbuf);
12671         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12672     }
12673
12674     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12675        counts the games, notes how each one terminated, etc.
12676
12677        It would be nice to remove this kludge and instead gather all
12678        the information while building the game list.  (And to keep it
12679        in the game list nodes instead of having a bunch of fixed-size
12680        parallel arrays.)  Note this will require getting each game's
12681        termination from the PGN tags, as the game list builder does
12682        not process the game moves.  --mann
12683        */
12684     cmailMsgLoaded = TRUE;
12685     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12686
12687     /* Load first game in the file or popup game menu */
12688     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12689
12690 #endif /* !WIN32 */
12691     return;
12692 }
12693
12694 int
12695 RegisterMove()
12696 {
12697     FILE *f;
12698     char string[MSG_SIZ];
12699
12700     if (   cmailMailedMove
12701         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12702         return TRUE;            /* Allow free viewing  */
12703     }
12704
12705     /* Unregister move to ensure that we don't leave RegisterMove        */
12706     /* with the move registered when the conditions for registering no   */
12707     /* longer hold                                                       */
12708     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12709         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12710         nCmailMovesRegistered --;
12711
12712         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12713           {
12714               free(cmailCommentList[lastLoadGameNumber - 1]);
12715               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12716           }
12717     }
12718
12719     if (cmailOldMove == -1) {
12720         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12721         return FALSE;
12722     }
12723
12724     if (currentMove > cmailOldMove + 1) {
12725         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12726         return FALSE;
12727     }
12728
12729     if (currentMove < cmailOldMove) {
12730         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12731         return FALSE;
12732     }
12733
12734     if (forwardMostMove > currentMove) {
12735         /* Silently truncate extra moves */
12736         TruncateGame();
12737     }
12738
12739     if (   (currentMove == cmailOldMove + 1)
12740         || (   (currentMove == cmailOldMove)
12741             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12742                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12743         if (gameInfo.result != GameUnfinished) {
12744             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12745         }
12746
12747         if (commentList[currentMove] != NULL) {
12748             cmailCommentList[lastLoadGameNumber - 1]
12749               = StrSave(commentList[currentMove]);
12750         }
12751         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12752
12753         if (appData.debugMode)
12754           fprintf(debugFP, "Saving %s for game %d\n",
12755                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12756
12757         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12758
12759         f = fopen(string, "w");
12760         if (appData.oldSaveStyle) {
12761             SaveGameOldStyle(f); /* also closes the file */
12762
12763             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12764             f = fopen(string, "w");
12765             SavePosition(f, 0, NULL); /* also closes the file */
12766         } else {
12767             fprintf(f, "{--------------\n");
12768             PrintPosition(f, currentMove);
12769             fprintf(f, "--------------}\n\n");
12770
12771             SaveGame(f, 0, NULL); /* also closes the file*/
12772         }
12773
12774         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12775         nCmailMovesRegistered ++;
12776     } else if (nCmailGames == 1) {
12777         DisplayError(_("You have not made a move yet"), 0);
12778         return FALSE;
12779     }
12780
12781     return TRUE;
12782 }
12783
12784 void
12785 MailMoveEvent()
12786 {
12787 #if !WIN32
12788     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12789     FILE *commandOutput;
12790     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12791     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12792     int nBuffers;
12793     int i;
12794     int archived;
12795     char *arcDir;
12796
12797     if (! cmailMsgLoaded) {
12798         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12799         return;
12800     }
12801
12802     if (nCmailGames == nCmailResults) {
12803         DisplayError(_("No unfinished games"), 0);
12804         return;
12805     }
12806
12807 #if CMAIL_PROHIBIT_REMAIL
12808     if (cmailMailedMove) {
12809       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);
12810         DisplayError(msg, 0);
12811         return;
12812     }
12813 #endif
12814
12815     if (! (cmailMailedMove || RegisterMove())) return;
12816
12817     if (   cmailMailedMove
12818         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12819       snprintf(string, MSG_SIZ, partCommandString,
12820                appData.debugMode ? " -v" : "", appData.cmailGameName);
12821         commandOutput = popen(string, "r");
12822
12823         if (commandOutput == NULL) {
12824             DisplayError(_("Failed to invoke cmail"), 0);
12825         } else {
12826             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12827                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12828             }
12829             if (nBuffers > 1) {
12830                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12831                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12832                 nBytes = MSG_SIZ - 1;
12833             } else {
12834                 (void) memcpy(msg, buffer, nBytes);
12835             }
12836             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12837
12838             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12839                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12840
12841                 archived = TRUE;
12842                 for (i = 0; i < nCmailGames; i ++) {
12843                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12844                         archived = FALSE;
12845                     }
12846                 }
12847                 if (   archived
12848                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12849                         != NULL)) {
12850                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12851                            arcDir,
12852                            appData.cmailGameName,
12853                            gameInfo.date);
12854                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12855                     cmailMsgLoaded = FALSE;
12856                 }
12857             }
12858
12859             DisplayInformation(msg);
12860             pclose(commandOutput);
12861         }
12862     } else {
12863         if ((*cmailMsg) != '\0') {
12864             DisplayInformation(cmailMsg);
12865         }
12866     }
12867
12868     return;
12869 #endif /* !WIN32 */
12870 }
12871
12872 char *
12873 CmailMsg()
12874 {
12875 #if WIN32
12876     return NULL;
12877 #else
12878     int  prependComma = 0;
12879     char number[5];
12880     char string[MSG_SIZ];       /* Space for game-list */
12881     int  i;
12882
12883     if (!cmailMsgLoaded) return "";
12884
12885     if (cmailMailedMove) {
12886       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12887     } else {
12888         /* Create a list of games left */
12889       snprintf(string, MSG_SIZ, "[");
12890         for (i = 0; i < nCmailGames; i ++) {
12891             if (! (   cmailMoveRegistered[i]
12892                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12893                 if (prependComma) {
12894                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12895                 } else {
12896                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12897                     prependComma = 1;
12898                 }
12899
12900                 strcat(string, number);
12901             }
12902         }
12903         strcat(string, "]");
12904
12905         if (nCmailMovesRegistered + nCmailResults == 0) {
12906             switch (nCmailGames) {
12907               case 1:
12908                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12909                 break;
12910
12911               case 2:
12912                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12913                 break;
12914
12915               default:
12916                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12917                          nCmailGames);
12918                 break;
12919             }
12920         } else {
12921             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12922               case 1:
12923                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12924                          string);
12925                 break;
12926
12927               case 0:
12928                 if (nCmailResults == nCmailGames) {
12929                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12930                 } else {
12931                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12932                 }
12933                 break;
12934
12935               default:
12936                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12937                          string);
12938             }
12939         }
12940     }
12941     return cmailMsg;
12942 #endif /* WIN32 */
12943 }
12944
12945 void
12946 ResetGameEvent()
12947 {
12948     if (gameMode == Training)
12949       SetTrainingModeOff();
12950
12951     Reset(TRUE, TRUE);
12952     cmailMsgLoaded = FALSE;
12953     if (appData.icsActive) {
12954       SendToICS(ics_prefix);
12955       SendToICS("refresh\n");
12956     }
12957 }
12958
12959 void
12960 ExitEvent(status)
12961      int status;
12962 {
12963     exiting++;
12964     if (exiting > 2) {
12965       /* Give up on clean exit */
12966       exit(status);
12967     }
12968     if (exiting > 1) {
12969       /* Keep trying for clean exit */
12970       return;
12971     }
12972
12973     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12974
12975     if (telnetISR != NULL) {
12976       RemoveInputSource(telnetISR);
12977     }
12978     if (icsPR != NoProc) {
12979       DestroyChildProcess(icsPR, TRUE);
12980     }
12981
12982     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12983     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12984
12985     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12986     /* make sure this other one finishes before killing it!                  */
12987     if(endingGame) { int count = 0;
12988         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12989         while(endingGame && count++ < 10) DoSleep(1);
12990         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12991     }
12992
12993     /* Kill off chess programs */
12994     if (first.pr != NoProc) {
12995         ExitAnalyzeMode();
12996
12997         DoSleep( appData.delayBeforeQuit );
12998         SendToProgram("quit\n", &first);
12999         DoSleep( appData.delayAfterQuit );
13000         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13001     }
13002     if (second.pr != NoProc) {
13003         DoSleep( appData.delayBeforeQuit );
13004         SendToProgram("quit\n", &second);
13005         DoSleep( appData.delayAfterQuit );
13006         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13007     }
13008     if (first.isr != NULL) {
13009         RemoveInputSource(first.isr);
13010     }
13011     if (second.isr != NULL) {
13012         RemoveInputSource(second.isr);
13013     }
13014
13015     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13016     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13017
13018     ShutDownFrontEnd();
13019     exit(status);
13020 }
13021
13022 void
13023 PauseEvent()
13024 {
13025     if (appData.debugMode)
13026         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13027     if (pausing) {
13028         pausing = FALSE;
13029         ModeHighlight();
13030         if (gameMode == MachinePlaysWhite ||
13031             gameMode == MachinePlaysBlack) {
13032             StartClocks();
13033         } else {
13034             DisplayBothClocks();
13035         }
13036         if (gameMode == PlayFromGameFile) {
13037             if (appData.timeDelay >= 0)
13038                 AutoPlayGameLoop();
13039         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13040             Reset(FALSE, TRUE);
13041             SendToICS(ics_prefix);
13042             SendToICS("refresh\n");
13043         } else if (currentMove < forwardMostMove) {
13044             ForwardInner(forwardMostMove);
13045         }
13046         pauseExamInvalid = FALSE;
13047     } else {
13048         switch (gameMode) {
13049           default:
13050             return;
13051           case IcsExamining:
13052             pauseExamForwardMostMove = forwardMostMove;
13053             pauseExamInvalid = FALSE;
13054             /* fall through */
13055           case IcsObserving:
13056           case IcsPlayingWhite:
13057           case IcsPlayingBlack:
13058             pausing = TRUE;
13059             ModeHighlight();
13060             return;
13061           case PlayFromGameFile:
13062             (void) StopLoadGameTimer();
13063             pausing = TRUE;
13064             ModeHighlight();
13065             break;
13066           case BeginningOfGame:
13067             if (appData.icsActive) return;
13068             /* else fall through */
13069           case MachinePlaysWhite:
13070           case MachinePlaysBlack:
13071           case TwoMachinesPlay:
13072             if (forwardMostMove == 0)
13073               return;           /* don't pause if no one has moved */
13074             if ((gameMode == MachinePlaysWhite &&
13075                  !WhiteOnMove(forwardMostMove)) ||
13076                 (gameMode == MachinePlaysBlack &&
13077                  WhiteOnMove(forwardMostMove))) {
13078                 StopClocks();
13079             }
13080             pausing = TRUE;
13081             ModeHighlight();
13082             break;
13083         }
13084     }
13085 }
13086
13087 void
13088 EditCommentEvent()
13089 {
13090     char title[MSG_SIZ];
13091
13092     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13093       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13094     } else {
13095       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13096                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13097                parseList[currentMove - 1]);
13098     }
13099
13100     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13101 }
13102
13103
13104 void
13105 EditTagsEvent()
13106 {
13107     char *tags = PGNTags(&gameInfo);
13108     bookUp = FALSE;
13109     EditTagsPopUp(tags, NULL);
13110     free(tags);
13111 }
13112
13113 void
13114 AnalyzeModeEvent()
13115 {
13116     if (appData.noChessProgram || gameMode == AnalyzeMode)
13117       return;
13118
13119     if (gameMode != AnalyzeFile) {
13120         if (!appData.icsEngineAnalyze) {
13121                EditGameEvent();
13122                if (gameMode != EditGame) return;
13123         }
13124         ResurrectChessProgram();
13125         SendToProgram("analyze\n", &first);
13126         first.analyzing = TRUE;
13127         /*first.maybeThinking = TRUE;*/
13128         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13129         EngineOutputPopUp();
13130     }
13131     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13132     pausing = FALSE;
13133     ModeHighlight();
13134     SetGameInfo();
13135
13136     StartAnalysisClock();
13137     GetTimeMark(&lastNodeCountTime);
13138     lastNodeCount = 0;
13139 }
13140
13141 void
13142 AnalyzeFileEvent()
13143 {
13144     if (appData.noChessProgram || gameMode == AnalyzeFile)
13145       return;
13146
13147     if (gameMode != AnalyzeMode) {
13148         EditGameEvent();
13149         if (gameMode != EditGame) return;
13150         ResurrectChessProgram();
13151         SendToProgram("analyze\n", &first);
13152         first.analyzing = TRUE;
13153         /*first.maybeThinking = TRUE;*/
13154         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13155         EngineOutputPopUp();
13156     }
13157     gameMode = AnalyzeFile;
13158     pausing = FALSE;
13159     ModeHighlight();
13160     SetGameInfo();
13161
13162     StartAnalysisClock();
13163     GetTimeMark(&lastNodeCountTime);
13164     lastNodeCount = 0;
13165     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13166 }
13167
13168 void
13169 MachineWhiteEvent()
13170 {
13171     char buf[MSG_SIZ];
13172     char *bookHit = NULL;
13173
13174     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13175       return;
13176
13177
13178     if (gameMode == PlayFromGameFile ||
13179         gameMode == TwoMachinesPlay  ||
13180         gameMode == Training         ||
13181         gameMode == AnalyzeMode      ||
13182         gameMode == EndOfGame)
13183         EditGameEvent();
13184
13185     if (gameMode == EditPosition)
13186         EditPositionDone(TRUE);
13187
13188     if (!WhiteOnMove(currentMove)) {
13189         DisplayError(_("It is not White's turn"), 0);
13190         return;
13191     }
13192
13193     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13194       ExitAnalyzeMode();
13195
13196     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13197         gameMode == AnalyzeFile)
13198         TruncateGame();
13199
13200     ResurrectChessProgram();    /* in case it isn't running */
13201     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13202         gameMode = MachinePlaysWhite;
13203         ResetClocks();
13204     } else
13205     gameMode = MachinePlaysWhite;
13206     pausing = FALSE;
13207     ModeHighlight();
13208     SetGameInfo();
13209     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13210     DisplayTitle(buf);
13211     if (first.sendName) {
13212       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13213       SendToProgram(buf, &first);
13214     }
13215     if (first.sendTime) {
13216       if (first.useColors) {
13217         SendToProgram("black\n", &first); /*gnu kludge*/
13218       }
13219       SendTimeRemaining(&first, TRUE);
13220     }
13221     if (first.useColors) {
13222       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13223     }
13224     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13225     SetMachineThinkingEnables();
13226     first.maybeThinking = TRUE;
13227     StartClocks();
13228     firstMove = FALSE;
13229
13230     if (appData.autoFlipView && !flipView) {
13231       flipView = !flipView;
13232       DrawPosition(FALSE, NULL);
13233       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13234     }
13235
13236     if(bookHit) { // [HGM] book: simulate book reply
13237         static char bookMove[MSG_SIZ]; // a bit generous?
13238
13239         programStats.nodes = programStats.depth = programStats.time =
13240         programStats.score = programStats.got_only_move = 0;
13241         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13242
13243         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13244         strcat(bookMove, bookHit);
13245         HandleMachineMove(bookMove, &first);
13246     }
13247 }
13248
13249 void
13250 MachineBlackEvent()
13251 {
13252   char buf[MSG_SIZ];
13253   char *bookHit = NULL;
13254
13255     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13256         return;
13257
13258
13259     if (gameMode == PlayFromGameFile ||
13260         gameMode == TwoMachinesPlay  ||
13261         gameMode == Training         ||
13262         gameMode == AnalyzeMode      ||
13263         gameMode == EndOfGame)
13264         EditGameEvent();
13265
13266     if (gameMode == EditPosition)
13267         EditPositionDone(TRUE);
13268
13269     if (WhiteOnMove(currentMove)) {
13270         DisplayError(_("It is not Black's turn"), 0);
13271         return;
13272     }
13273
13274     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13275       ExitAnalyzeMode();
13276
13277     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13278         gameMode == AnalyzeFile)
13279         TruncateGame();
13280
13281     ResurrectChessProgram();    /* in case it isn't running */
13282     gameMode = MachinePlaysBlack;
13283     pausing = FALSE;
13284     ModeHighlight();
13285     SetGameInfo();
13286     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13287     DisplayTitle(buf);
13288     if (first.sendName) {
13289       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13290       SendToProgram(buf, &first);
13291     }
13292     if (first.sendTime) {
13293       if (first.useColors) {
13294         SendToProgram("white\n", &first); /*gnu kludge*/
13295       }
13296       SendTimeRemaining(&first, FALSE);
13297     }
13298     if (first.useColors) {
13299       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13300     }
13301     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13302     SetMachineThinkingEnables();
13303     first.maybeThinking = TRUE;
13304     StartClocks();
13305
13306     if (appData.autoFlipView && flipView) {
13307       flipView = !flipView;
13308       DrawPosition(FALSE, NULL);
13309       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13310     }
13311     if(bookHit) { // [HGM] book: simulate book reply
13312         static char bookMove[MSG_SIZ]; // a bit generous?
13313
13314         programStats.nodes = programStats.depth = programStats.time =
13315         programStats.score = programStats.got_only_move = 0;
13316         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13317
13318         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13319         strcat(bookMove, bookHit);
13320         HandleMachineMove(bookMove, &first);
13321     }
13322 }
13323
13324
13325 void
13326 DisplayTwoMachinesTitle()
13327 {
13328     char buf[MSG_SIZ];
13329     if (appData.matchGames > 0) {
13330         if(appData.tourneyFile[0]) {
13331           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13332                    gameInfo.white, gameInfo.black,
13333                    nextGame+1, appData.matchGames+1,
13334                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13335         } else 
13336         if (first.twoMachinesColor[0] == 'w') {
13337           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13338                    gameInfo.white, gameInfo.black,
13339                    first.matchWins, second.matchWins,
13340                    matchGame - 1 - (first.matchWins + second.matchWins));
13341         } else {
13342           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13343                    gameInfo.white, gameInfo.black,
13344                    second.matchWins, first.matchWins,
13345                    matchGame - 1 - (first.matchWins + second.matchWins));
13346         }
13347     } else {
13348       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13349     }
13350     DisplayTitle(buf);
13351 }
13352
13353 void
13354 SettingsMenuIfReady()
13355 {
13356   if (second.lastPing != second.lastPong) {
13357     DisplayMessage("", _("Waiting for second chess program"));
13358     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13359     return;
13360   }
13361   ThawUI();
13362   DisplayMessage("", "");
13363   SettingsPopUp(&second);
13364 }
13365
13366 int
13367 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13368 {
13369     char buf[MSG_SIZ];
13370     if (cps->pr == NoProc) {
13371         StartChessProgram(cps);
13372         if (cps->protocolVersion == 1) {
13373           retry();
13374         } else {
13375           /* kludge: allow timeout for initial "feature" command */
13376           FreezeUI();
13377           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13378           DisplayMessage("", buf);
13379           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13380         }
13381         return 1;
13382     }
13383     return 0;
13384 }
13385
13386 void
13387 TwoMachinesEvent P((void))
13388 {
13389     int i;
13390     char buf[MSG_SIZ];
13391     ChessProgramState *onmove;
13392     char *bookHit = NULL;
13393     static int stalling = 0;
13394     TimeMark now;
13395     long wait;
13396
13397     if (appData.noChessProgram) return;
13398
13399     switch (gameMode) {
13400       case TwoMachinesPlay:
13401         return;
13402       case MachinePlaysWhite:
13403       case MachinePlaysBlack:
13404         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13405             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13406             return;
13407         }
13408         /* fall through */
13409       case BeginningOfGame:
13410       case PlayFromGameFile:
13411       case EndOfGame:
13412         EditGameEvent();
13413         if (gameMode != EditGame) return;
13414         break;
13415       case EditPosition:
13416         EditPositionDone(TRUE);
13417         break;
13418       case AnalyzeMode:
13419       case AnalyzeFile:
13420         ExitAnalyzeMode();
13421         break;
13422       case EditGame:
13423       default:
13424         break;
13425     }
13426
13427 //    forwardMostMove = currentMove;
13428     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13429
13430     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13431
13432     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13433     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13434       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13435       return;
13436     }
13437     if(!stalling) {
13438       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13439       SendToProgram("force\n", &second);
13440       stalling = 1;
13441       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13442       return;
13443     }
13444     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13445     if(appData.matchPause>10000 || appData.matchPause<10)
13446                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13447     wait = SubtractTimeMarks(&now, &pauseStart);
13448     if(wait < appData.matchPause) {
13449         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13450         return;
13451     }
13452     stalling = 0;
13453     DisplayMessage("", "");
13454     if (startedFromSetupPosition) {
13455         SendBoard(&second, backwardMostMove);
13456     if (appData.debugMode) {
13457         fprintf(debugFP, "Two Machines\n");
13458     }
13459     }
13460     for (i = backwardMostMove; i < forwardMostMove; i++) {
13461         SendMoveToProgram(i, &second);
13462     }
13463
13464     gameMode = TwoMachinesPlay;
13465     pausing = FALSE;
13466     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13467     SetGameInfo();
13468     DisplayTwoMachinesTitle();
13469     firstMove = TRUE;
13470     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13471         onmove = &first;
13472     } else {
13473         onmove = &second;
13474     }
13475     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13476     SendToProgram(first.computerString, &first);
13477     if (first.sendName) {
13478       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13479       SendToProgram(buf, &first);
13480     }
13481     SendToProgram(second.computerString, &second);
13482     if (second.sendName) {
13483       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13484       SendToProgram(buf, &second);
13485     }
13486
13487     ResetClocks();
13488     if (!first.sendTime || !second.sendTime) {
13489         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13490         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13491     }
13492     if (onmove->sendTime) {
13493       if (onmove->useColors) {
13494         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13495       }
13496       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13497     }
13498     if (onmove->useColors) {
13499       SendToProgram(onmove->twoMachinesColor, onmove);
13500     }
13501     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13502 //    SendToProgram("go\n", onmove);
13503     onmove->maybeThinking = TRUE;
13504     SetMachineThinkingEnables();
13505
13506     StartClocks();
13507
13508     if(bookHit) { // [HGM] book: simulate book reply
13509         static char bookMove[MSG_SIZ]; // a bit generous?
13510
13511         programStats.nodes = programStats.depth = programStats.time =
13512         programStats.score = programStats.got_only_move = 0;
13513         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13514
13515         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13516         strcat(bookMove, bookHit);
13517         savedMessage = bookMove; // args for deferred call
13518         savedState = onmove;
13519         ScheduleDelayedEvent(DeferredBookMove, 1);
13520     }
13521 }
13522
13523 void
13524 TrainingEvent()
13525 {
13526     if (gameMode == Training) {
13527       SetTrainingModeOff();
13528       gameMode = PlayFromGameFile;
13529       DisplayMessage("", _("Training mode off"));
13530     } else {
13531       gameMode = Training;
13532       animateTraining = appData.animate;
13533
13534       /* make sure we are not already at the end of the game */
13535       if (currentMove < forwardMostMove) {
13536         SetTrainingModeOn();
13537         DisplayMessage("", _("Training mode on"));
13538       } else {
13539         gameMode = PlayFromGameFile;
13540         DisplayError(_("Already at end of game"), 0);
13541       }
13542     }
13543     ModeHighlight();
13544 }
13545
13546 void
13547 IcsClientEvent()
13548 {
13549     if (!appData.icsActive) return;
13550     switch (gameMode) {
13551       case IcsPlayingWhite:
13552       case IcsPlayingBlack:
13553       case IcsObserving:
13554       case IcsIdle:
13555       case BeginningOfGame:
13556       case IcsExamining:
13557         return;
13558
13559       case EditGame:
13560         break;
13561
13562       case EditPosition:
13563         EditPositionDone(TRUE);
13564         break;
13565
13566       case AnalyzeMode:
13567       case AnalyzeFile:
13568         ExitAnalyzeMode();
13569         break;
13570
13571       default:
13572         EditGameEvent();
13573         break;
13574     }
13575
13576     gameMode = IcsIdle;
13577     ModeHighlight();
13578     return;
13579 }
13580
13581
13582 void
13583 EditGameEvent()
13584 {
13585     int i;
13586
13587     switch (gameMode) {
13588       case Training:
13589         SetTrainingModeOff();
13590         break;
13591       case MachinePlaysWhite:
13592       case MachinePlaysBlack:
13593       case BeginningOfGame:
13594         SendToProgram("force\n", &first);
13595         SetUserThinkingEnables();
13596         break;
13597       case PlayFromGameFile:
13598         (void) StopLoadGameTimer();
13599         if (gameFileFP != NULL) {
13600             gameFileFP = NULL;
13601         }
13602         break;
13603       case EditPosition:
13604         EditPositionDone(TRUE);
13605         break;
13606       case AnalyzeMode:
13607       case AnalyzeFile:
13608         ExitAnalyzeMode();
13609         SendToProgram("force\n", &first);
13610         break;
13611       case TwoMachinesPlay:
13612         GameEnds(EndOfFile, NULL, GE_PLAYER);
13613         ResurrectChessProgram();
13614         SetUserThinkingEnables();
13615         break;
13616       case EndOfGame:
13617         ResurrectChessProgram();
13618         break;
13619       case IcsPlayingBlack:
13620       case IcsPlayingWhite:
13621         DisplayError(_("Warning: You are still playing a game"), 0);
13622         break;
13623       case IcsObserving:
13624         DisplayError(_("Warning: You are still observing a game"), 0);
13625         break;
13626       case IcsExamining:
13627         DisplayError(_("Warning: You are still examining a game"), 0);
13628         break;
13629       case IcsIdle:
13630         break;
13631       case EditGame:
13632       default:
13633         return;
13634     }
13635
13636     pausing = FALSE;
13637     StopClocks();
13638     first.offeredDraw = second.offeredDraw = 0;
13639
13640     if (gameMode == PlayFromGameFile) {
13641         whiteTimeRemaining = timeRemaining[0][currentMove];
13642         blackTimeRemaining = timeRemaining[1][currentMove];
13643         DisplayTitle("");
13644     }
13645
13646     if (gameMode == MachinePlaysWhite ||
13647         gameMode == MachinePlaysBlack ||
13648         gameMode == TwoMachinesPlay ||
13649         gameMode == EndOfGame) {
13650         i = forwardMostMove;
13651         while (i > currentMove) {
13652             SendToProgram("undo\n", &first);
13653             i--;
13654         }
13655         if(!adjustedClock) {
13656         whiteTimeRemaining = timeRemaining[0][currentMove];
13657         blackTimeRemaining = timeRemaining[1][currentMove];
13658         DisplayBothClocks();
13659         }
13660         if (whiteFlag || blackFlag) {
13661             whiteFlag = blackFlag = 0;
13662         }
13663         DisplayTitle("");
13664     }
13665
13666     gameMode = EditGame;
13667     ModeHighlight();
13668     SetGameInfo();
13669 }
13670
13671
13672 void
13673 EditPositionEvent()
13674 {
13675     if (gameMode == EditPosition) {
13676         EditGameEvent();
13677         return;
13678     }
13679
13680     EditGameEvent();
13681     if (gameMode != EditGame) return;
13682
13683     gameMode = EditPosition;
13684     ModeHighlight();
13685     SetGameInfo();
13686     if (currentMove > 0)
13687       CopyBoard(boards[0], boards[currentMove]);
13688
13689     blackPlaysFirst = !WhiteOnMove(currentMove);
13690     ResetClocks();
13691     currentMove = forwardMostMove = backwardMostMove = 0;
13692     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13693     DisplayMove(-1);
13694 }
13695
13696 void
13697 ExitAnalyzeMode()
13698 {
13699     /* [DM] icsEngineAnalyze - possible call from other functions */
13700     if (appData.icsEngineAnalyze) {
13701         appData.icsEngineAnalyze = FALSE;
13702
13703         DisplayMessage("",_("Close ICS engine analyze..."));
13704     }
13705     if (first.analysisSupport && first.analyzing) {
13706       SendToProgram("exit\n", &first);
13707       first.analyzing = FALSE;
13708     }
13709     thinkOutput[0] = NULLCHAR;
13710 }
13711
13712 void
13713 EditPositionDone(Boolean fakeRights)
13714 {
13715     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13716
13717     startedFromSetupPosition = TRUE;
13718     InitChessProgram(&first, FALSE);
13719     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13720       boards[0][EP_STATUS] = EP_NONE;
13721       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13722     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13723         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13724         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13725       } else boards[0][CASTLING][2] = NoRights;
13726     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13727         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13728         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13729       } else boards[0][CASTLING][5] = NoRights;
13730     }
13731     SendToProgram("force\n", &first);
13732     if (blackPlaysFirst) {
13733         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13734         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13735         currentMove = forwardMostMove = backwardMostMove = 1;
13736         CopyBoard(boards[1], boards[0]);
13737     } else {
13738         currentMove = forwardMostMove = backwardMostMove = 0;
13739     }
13740     SendBoard(&first, forwardMostMove);
13741     if (appData.debugMode) {
13742         fprintf(debugFP, "EditPosDone\n");
13743     }
13744     DisplayTitle("");
13745     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13746     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13747     gameMode = EditGame;
13748     ModeHighlight();
13749     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13750     ClearHighlights(); /* [AS] */
13751 }
13752
13753 /* Pause for `ms' milliseconds */
13754 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13755 void
13756 TimeDelay(ms)
13757      long ms;
13758 {
13759     TimeMark m1, m2;
13760
13761     GetTimeMark(&m1);
13762     do {
13763         GetTimeMark(&m2);
13764     } while (SubtractTimeMarks(&m2, &m1) < ms);
13765 }
13766
13767 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13768 void
13769 SendMultiLineToICS(buf)
13770      char *buf;
13771 {
13772     char temp[MSG_SIZ+1], *p;
13773     int len;
13774
13775     len = strlen(buf);
13776     if (len > MSG_SIZ)
13777       len = MSG_SIZ;
13778
13779     strncpy(temp, buf, len);
13780     temp[len] = 0;
13781
13782     p = temp;
13783     while (*p) {
13784         if (*p == '\n' || *p == '\r')
13785           *p = ' ';
13786         ++p;
13787     }
13788
13789     strcat(temp, "\n");
13790     SendToICS(temp);
13791     SendToPlayer(temp, strlen(temp));
13792 }
13793
13794 void
13795 SetWhiteToPlayEvent()
13796 {
13797     if (gameMode == EditPosition) {
13798         blackPlaysFirst = FALSE;
13799         DisplayBothClocks();    /* works because currentMove is 0 */
13800     } else if (gameMode == IcsExamining) {
13801         SendToICS(ics_prefix);
13802         SendToICS("tomove white\n");
13803     }
13804 }
13805
13806 void
13807 SetBlackToPlayEvent()
13808 {
13809     if (gameMode == EditPosition) {
13810         blackPlaysFirst = TRUE;
13811         currentMove = 1;        /* kludge */
13812         DisplayBothClocks();
13813         currentMove = 0;
13814     } else if (gameMode == IcsExamining) {
13815         SendToICS(ics_prefix);
13816         SendToICS("tomove black\n");
13817     }
13818 }
13819
13820 void
13821 EditPositionMenuEvent(selection, x, y)
13822      ChessSquare selection;
13823      int x, y;
13824 {
13825     char buf[MSG_SIZ];
13826     ChessSquare piece = boards[0][y][x];
13827
13828     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13829
13830     switch (selection) {
13831       case ClearBoard:
13832         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13833             SendToICS(ics_prefix);
13834             SendToICS("bsetup clear\n");
13835         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13836             SendToICS(ics_prefix);
13837             SendToICS("clearboard\n");
13838         } else {
13839             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13840                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13841                 for (y = 0; y < BOARD_HEIGHT; y++) {
13842                     if (gameMode == IcsExamining) {
13843                         if (boards[currentMove][y][x] != EmptySquare) {
13844                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13845                                     AAA + x, ONE + y);
13846                             SendToICS(buf);
13847                         }
13848                     } else {
13849                         boards[0][y][x] = p;
13850                     }
13851                 }
13852             }
13853         }
13854         if (gameMode == EditPosition) {
13855             DrawPosition(FALSE, boards[0]);
13856         }
13857         break;
13858
13859       case WhitePlay:
13860         SetWhiteToPlayEvent();
13861         break;
13862
13863       case BlackPlay:
13864         SetBlackToPlayEvent();
13865         break;
13866
13867       case EmptySquare:
13868         if (gameMode == IcsExamining) {
13869             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13870             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13871             SendToICS(buf);
13872         } else {
13873             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13874                 if(x == BOARD_LEFT-2) {
13875                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13876                     boards[0][y][1] = 0;
13877                 } else
13878                 if(x == BOARD_RGHT+1) {
13879                     if(y >= gameInfo.holdingsSize) break;
13880                     boards[0][y][BOARD_WIDTH-2] = 0;
13881                 } else break;
13882             }
13883             boards[0][y][x] = EmptySquare;
13884             DrawPosition(FALSE, boards[0]);
13885         }
13886         break;
13887
13888       case PromotePiece:
13889         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13890            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13891             selection = (ChessSquare) (PROMOTED piece);
13892         } else if(piece == EmptySquare) selection = WhiteSilver;
13893         else selection = (ChessSquare)((int)piece - 1);
13894         goto defaultlabel;
13895
13896       case DemotePiece:
13897         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13898            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13899             selection = (ChessSquare) (DEMOTED piece);
13900         } else if(piece == EmptySquare) selection = BlackSilver;
13901         else selection = (ChessSquare)((int)piece + 1);
13902         goto defaultlabel;
13903
13904       case WhiteQueen:
13905       case BlackQueen:
13906         if(gameInfo.variant == VariantShatranj ||
13907            gameInfo.variant == VariantXiangqi  ||
13908            gameInfo.variant == VariantCourier  ||
13909            gameInfo.variant == VariantMakruk     )
13910             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13911         goto defaultlabel;
13912
13913       case WhiteKing:
13914       case BlackKing:
13915         if(gameInfo.variant == VariantXiangqi)
13916             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13917         if(gameInfo.variant == VariantKnightmate)
13918             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13919       default:
13920         defaultlabel:
13921         if (gameMode == IcsExamining) {
13922             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13923             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13924                      PieceToChar(selection), AAA + x, ONE + y);
13925             SendToICS(buf);
13926         } else {
13927             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13928                 int n;
13929                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13930                     n = PieceToNumber(selection - BlackPawn);
13931                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13932                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13933                     boards[0][BOARD_HEIGHT-1-n][1]++;
13934                 } else
13935                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13936                     n = PieceToNumber(selection);
13937                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13938                     boards[0][n][BOARD_WIDTH-1] = selection;
13939                     boards[0][n][BOARD_WIDTH-2]++;
13940                 }
13941             } else
13942             boards[0][y][x] = selection;
13943             DrawPosition(TRUE, boards[0]);
13944         }
13945         break;
13946     }
13947 }
13948
13949
13950 void
13951 DropMenuEvent(selection, x, y)
13952      ChessSquare selection;
13953      int x, y;
13954 {
13955     ChessMove moveType;
13956
13957     switch (gameMode) {
13958       case IcsPlayingWhite:
13959       case MachinePlaysBlack:
13960         if (!WhiteOnMove(currentMove)) {
13961             DisplayMoveError(_("It is Black's turn"));
13962             return;
13963         }
13964         moveType = WhiteDrop;
13965         break;
13966       case IcsPlayingBlack:
13967       case MachinePlaysWhite:
13968         if (WhiteOnMove(currentMove)) {
13969             DisplayMoveError(_("It is White's turn"));
13970             return;
13971         }
13972         moveType = BlackDrop;
13973         break;
13974       case EditGame:
13975         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13976         break;
13977       default:
13978         return;
13979     }
13980
13981     if (moveType == BlackDrop && selection < BlackPawn) {
13982       selection = (ChessSquare) ((int) selection
13983                                  + (int) BlackPawn - (int) WhitePawn);
13984     }
13985     if (boards[currentMove][y][x] != EmptySquare) {
13986         DisplayMoveError(_("That square is occupied"));
13987         return;
13988     }
13989
13990     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13991 }
13992
13993 void
13994 AcceptEvent()
13995 {
13996     /* Accept a pending offer of any kind from opponent */
13997
13998     if (appData.icsActive) {
13999         SendToICS(ics_prefix);
14000         SendToICS("accept\n");
14001     } else if (cmailMsgLoaded) {
14002         if (currentMove == cmailOldMove &&
14003             commentList[cmailOldMove] != NULL &&
14004             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14005                    "Black offers a draw" : "White offers a draw")) {
14006             TruncateGame();
14007             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14008             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14009         } else {
14010             DisplayError(_("There is no pending offer on this move"), 0);
14011             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14012         }
14013     } else {
14014         /* Not used for offers from chess program */
14015     }
14016 }
14017
14018 void
14019 DeclineEvent()
14020 {
14021     /* Decline a pending offer of any kind from opponent */
14022
14023     if (appData.icsActive) {
14024         SendToICS(ics_prefix);
14025         SendToICS("decline\n");
14026     } else if (cmailMsgLoaded) {
14027         if (currentMove == cmailOldMove &&
14028             commentList[cmailOldMove] != NULL &&
14029             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14030                    "Black offers a draw" : "White offers a draw")) {
14031 #ifdef NOTDEF
14032             AppendComment(cmailOldMove, "Draw declined", TRUE);
14033             DisplayComment(cmailOldMove - 1, "Draw declined");
14034 #endif /*NOTDEF*/
14035         } else {
14036             DisplayError(_("There is no pending offer on this move"), 0);
14037         }
14038     } else {
14039         /* Not used for offers from chess program */
14040     }
14041 }
14042
14043 void
14044 RematchEvent()
14045 {
14046     /* Issue ICS rematch command */
14047     if (appData.icsActive) {
14048         SendToICS(ics_prefix);
14049         SendToICS("rematch\n");
14050     }
14051 }
14052
14053 void
14054 CallFlagEvent()
14055 {
14056     /* Call your opponent's flag (claim a win on time) */
14057     if (appData.icsActive) {
14058         SendToICS(ics_prefix);
14059         SendToICS("flag\n");
14060     } else {
14061         switch (gameMode) {
14062           default:
14063             return;
14064           case MachinePlaysWhite:
14065             if (whiteFlag) {
14066                 if (blackFlag)
14067                   GameEnds(GameIsDrawn, "Both players ran out of time",
14068                            GE_PLAYER);
14069                 else
14070                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14071             } else {
14072                 DisplayError(_("Your opponent is not out of time"), 0);
14073             }
14074             break;
14075           case MachinePlaysBlack:
14076             if (blackFlag) {
14077                 if (whiteFlag)
14078                   GameEnds(GameIsDrawn, "Both players ran out of time",
14079                            GE_PLAYER);
14080                 else
14081                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14082             } else {
14083                 DisplayError(_("Your opponent is not out of time"), 0);
14084             }
14085             break;
14086         }
14087     }
14088 }
14089
14090 void
14091 ClockClick(int which)
14092 {       // [HGM] code moved to back-end from winboard.c
14093         if(which) { // black clock
14094           if (gameMode == EditPosition || gameMode == IcsExamining) {
14095             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14096             SetBlackToPlayEvent();
14097           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14098           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14099           } else if (shiftKey) {
14100             AdjustClock(which, -1);
14101           } else if (gameMode == IcsPlayingWhite ||
14102                      gameMode == MachinePlaysBlack) {
14103             CallFlagEvent();
14104           }
14105         } else { // white clock
14106           if (gameMode == EditPosition || gameMode == IcsExamining) {
14107             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14108             SetWhiteToPlayEvent();
14109           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14110           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14111           } else if (shiftKey) {
14112             AdjustClock(which, -1);
14113           } else if (gameMode == IcsPlayingBlack ||
14114                    gameMode == MachinePlaysWhite) {
14115             CallFlagEvent();
14116           }
14117         }
14118 }
14119
14120 void
14121 DrawEvent()
14122 {
14123     /* Offer draw or accept pending draw offer from opponent */
14124
14125     if (appData.icsActive) {
14126         /* Note: tournament rules require draw offers to be
14127            made after you make your move but before you punch
14128            your clock.  Currently ICS doesn't let you do that;
14129            instead, you immediately punch your clock after making
14130            a move, but you can offer a draw at any time. */
14131
14132         SendToICS(ics_prefix);
14133         SendToICS("draw\n");
14134         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14135     } else if (cmailMsgLoaded) {
14136         if (currentMove == cmailOldMove &&
14137             commentList[cmailOldMove] != NULL &&
14138             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14139                    "Black offers a draw" : "White offers a draw")) {
14140             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14141             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14142         } else if (currentMove == cmailOldMove + 1) {
14143             char *offer = WhiteOnMove(cmailOldMove) ?
14144               "White offers a draw" : "Black offers a draw";
14145             AppendComment(currentMove, offer, TRUE);
14146             DisplayComment(currentMove - 1, offer);
14147             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14148         } else {
14149             DisplayError(_("You must make your move before offering a draw"), 0);
14150             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14151         }
14152     } else if (first.offeredDraw) {
14153         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14154     } else {
14155         if (first.sendDrawOffers) {
14156             SendToProgram("draw\n", &first);
14157             userOfferedDraw = TRUE;
14158         }
14159     }
14160 }
14161
14162 void
14163 AdjournEvent()
14164 {
14165     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14166
14167     if (appData.icsActive) {
14168         SendToICS(ics_prefix);
14169         SendToICS("adjourn\n");
14170     } else {
14171         /* Currently GNU Chess doesn't offer or accept Adjourns */
14172     }
14173 }
14174
14175
14176 void
14177 AbortEvent()
14178 {
14179     /* Offer Abort or accept pending Abort offer from opponent */
14180
14181     if (appData.icsActive) {
14182         SendToICS(ics_prefix);
14183         SendToICS("abort\n");
14184     } else {
14185         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14186     }
14187 }
14188
14189 void
14190 ResignEvent()
14191 {
14192     /* Resign.  You can do this even if it's not your turn. */
14193
14194     if (appData.icsActive) {
14195         SendToICS(ics_prefix);
14196         SendToICS("resign\n");
14197     } else {
14198         switch (gameMode) {
14199           case MachinePlaysWhite:
14200             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14201             break;
14202           case MachinePlaysBlack:
14203             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14204             break;
14205           case EditGame:
14206             if (cmailMsgLoaded) {
14207                 TruncateGame();
14208                 if (WhiteOnMove(cmailOldMove)) {
14209                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14210                 } else {
14211                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14212                 }
14213                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14214             }
14215             break;
14216           default:
14217             break;
14218         }
14219     }
14220 }
14221
14222
14223 void
14224 StopObservingEvent()
14225 {
14226     /* Stop observing current games */
14227     SendToICS(ics_prefix);
14228     SendToICS("unobserve\n");
14229 }
14230
14231 void
14232 StopExaminingEvent()
14233 {
14234     /* Stop observing current game */
14235     SendToICS(ics_prefix);
14236     SendToICS("unexamine\n");
14237 }
14238
14239 void
14240 ForwardInner(target)
14241      int target;
14242 {
14243     int limit;
14244
14245     if (appData.debugMode)
14246         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14247                 target, currentMove, forwardMostMove);
14248
14249     if (gameMode == EditPosition)
14250       return;
14251
14252     MarkTargetSquares(1);
14253
14254     if (gameMode == PlayFromGameFile && !pausing)
14255       PauseEvent();
14256
14257     if (gameMode == IcsExamining && pausing)
14258       limit = pauseExamForwardMostMove;
14259     else
14260       limit = forwardMostMove;
14261
14262     if (target > limit) target = limit;
14263
14264     if (target > 0 && moveList[target - 1][0]) {
14265         int fromX, fromY, toX, toY;
14266         toX = moveList[target - 1][2] - AAA;
14267         toY = moveList[target - 1][3] - ONE;
14268         if (moveList[target - 1][1] == '@') {
14269             if (appData.highlightLastMove) {
14270                 SetHighlights(-1, -1, toX, toY);
14271             }
14272         } else {
14273             fromX = moveList[target - 1][0] - AAA;
14274             fromY = moveList[target - 1][1] - ONE;
14275             if (target == currentMove + 1) {
14276                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14277             }
14278             if (appData.highlightLastMove) {
14279                 SetHighlights(fromX, fromY, toX, toY);
14280             }
14281         }
14282     }
14283     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14284         gameMode == Training || gameMode == PlayFromGameFile ||
14285         gameMode == AnalyzeFile) {
14286         while (currentMove < target) {
14287             SendMoveToProgram(currentMove++, &first);
14288         }
14289     } else {
14290         currentMove = target;
14291     }
14292
14293     if (gameMode == EditGame || gameMode == EndOfGame) {
14294         whiteTimeRemaining = timeRemaining[0][currentMove];
14295         blackTimeRemaining = timeRemaining[1][currentMove];
14296     }
14297     DisplayBothClocks();
14298     DisplayMove(currentMove - 1);
14299     DrawPosition(FALSE, boards[currentMove]);
14300     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14301     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14302         DisplayComment(currentMove - 1, commentList[currentMove]);
14303     }
14304 }
14305
14306
14307 void
14308 ForwardEvent()
14309 {
14310     if (gameMode == IcsExamining && !pausing) {
14311         SendToICS(ics_prefix);
14312         SendToICS("forward\n");
14313     } else {
14314         ForwardInner(currentMove + 1);
14315     }
14316 }
14317
14318 void
14319 ToEndEvent()
14320 {
14321     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14322         /* to optimze, we temporarily turn off analysis mode while we feed
14323          * the remaining moves to the engine. Otherwise we get analysis output
14324          * after each move.
14325          */
14326         if (first.analysisSupport) {
14327           SendToProgram("exit\nforce\n", &first);
14328           first.analyzing = FALSE;
14329         }
14330     }
14331
14332     if (gameMode == IcsExamining && !pausing) {
14333         SendToICS(ics_prefix);
14334         SendToICS("forward 999999\n");
14335     } else {
14336         ForwardInner(forwardMostMove);
14337     }
14338
14339     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14340         /* we have fed all the moves, so reactivate analysis mode */
14341         SendToProgram("analyze\n", &first);
14342         first.analyzing = TRUE;
14343         /*first.maybeThinking = TRUE;*/
14344         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14345     }
14346 }
14347
14348 void
14349 BackwardInner(target)
14350      int target;
14351 {
14352     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14353
14354     if (appData.debugMode)
14355         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14356                 target, currentMove, forwardMostMove);
14357
14358     if (gameMode == EditPosition) return;
14359     MarkTargetSquares(1);
14360     if (currentMove <= backwardMostMove) {
14361         ClearHighlights();
14362         DrawPosition(full_redraw, boards[currentMove]);
14363         return;
14364     }
14365     if (gameMode == PlayFromGameFile && !pausing)
14366       PauseEvent();
14367
14368     if (moveList[target][0]) {
14369         int fromX, fromY, toX, toY;
14370         toX = moveList[target][2] - AAA;
14371         toY = moveList[target][3] - ONE;
14372         if (moveList[target][1] == '@') {
14373             if (appData.highlightLastMove) {
14374                 SetHighlights(-1, -1, toX, toY);
14375             }
14376         } else {
14377             fromX = moveList[target][0] - AAA;
14378             fromY = moveList[target][1] - ONE;
14379             if (target == currentMove - 1) {
14380                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14381             }
14382             if (appData.highlightLastMove) {
14383                 SetHighlights(fromX, fromY, toX, toY);
14384             }
14385         }
14386     }
14387     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14388         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14389         while (currentMove > target) {
14390             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14391                 // null move cannot be undone. Reload program with move history before it.
14392                 int i;
14393                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14394                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14395                 }
14396                 SendBoard(&first, i); 
14397                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14398                 break;
14399             }
14400             SendToProgram("undo\n", &first);
14401             currentMove--;
14402         }
14403     } else {
14404         currentMove = target;
14405     }
14406
14407     if (gameMode == EditGame || gameMode == EndOfGame) {
14408         whiteTimeRemaining = timeRemaining[0][currentMove];
14409         blackTimeRemaining = timeRemaining[1][currentMove];
14410     }
14411     DisplayBothClocks();
14412     DisplayMove(currentMove - 1);
14413     DrawPosition(full_redraw, boards[currentMove]);
14414     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14415     // [HGM] PV info: routine tests if comment empty
14416     DisplayComment(currentMove - 1, commentList[currentMove]);
14417 }
14418
14419 void
14420 BackwardEvent()
14421 {
14422     if (gameMode == IcsExamining && !pausing) {
14423         SendToICS(ics_prefix);
14424         SendToICS("backward\n");
14425     } else {
14426         BackwardInner(currentMove - 1);
14427     }
14428 }
14429
14430 void
14431 ToStartEvent()
14432 {
14433     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14434         /* to optimize, we temporarily turn off analysis mode while we undo
14435          * all the moves. Otherwise we get analysis output after each undo.
14436          */
14437         if (first.analysisSupport) {
14438           SendToProgram("exit\nforce\n", &first);
14439           first.analyzing = FALSE;
14440         }
14441     }
14442
14443     if (gameMode == IcsExamining && !pausing) {
14444         SendToICS(ics_prefix);
14445         SendToICS("backward 999999\n");
14446     } else {
14447         BackwardInner(backwardMostMove);
14448     }
14449
14450     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14451         /* we have fed all the moves, so reactivate analysis mode */
14452         SendToProgram("analyze\n", &first);
14453         first.analyzing = TRUE;
14454         /*first.maybeThinking = TRUE;*/
14455         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14456     }
14457 }
14458
14459 void
14460 ToNrEvent(int to)
14461 {
14462   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14463   if (to >= forwardMostMove) to = forwardMostMove;
14464   if (to <= backwardMostMove) to = backwardMostMove;
14465   if (to < currentMove) {
14466     BackwardInner(to);
14467   } else {
14468     ForwardInner(to);
14469   }
14470 }
14471
14472 void
14473 RevertEvent(Boolean annotate)
14474 {
14475     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14476         return;
14477     }
14478     if (gameMode != IcsExamining) {
14479         DisplayError(_("You are not examining a game"), 0);
14480         return;
14481     }
14482     if (pausing) {
14483         DisplayError(_("You can't revert while pausing"), 0);
14484         return;
14485     }
14486     SendToICS(ics_prefix);
14487     SendToICS("revert\n");
14488 }
14489
14490 void
14491 RetractMoveEvent()
14492 {
14493     switch (gameMode) {
14494       case MachinePlaysWhite:
14495       case MachinePlaysBlack:
14496         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14497             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14498             return;
14499         }
14500         if (forwardMostMove < 2) return;
14501         currentMove = forwardMostMove = forwardMostMove - 2;
14502         whiteTimeRemaining = timeRemaining[0][currentMove];
14503         blackTimeRemaining = timeRemaining[1][currentMove];
14504         DisplayBothClocks();
14505         DisplayMove(currentMove - 1);
14506         ClearHighlights();/*!! could figure this out*/
14507         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14508         SendToProgram("remove\n", &first);
14509         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14510         break;
14511
14512       case BeginningOfGame:
14513       default:
14514         break;
14515
14516       case IcsPlayingWhite:
14517       case IcsPlayingBlack:
14518         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14519             SendToICS(ics_prefix);
14520             SendToICS("takeback 2\n");
14521         } else {
14522             SendToICS(ics_prefix);
14523             SendToICS("takeback 1\n");
14524         }
14525         break;
14526     }
14527 }
14528
14529 void
14530 MoveNowEvent()
14531 {
14532     ChessProgramState *cps;
14533
14534     switch (gameMode) {
14535       case MachinePlaysWhite:
14536         if (!WhiteOnMove(forwardMostMove)) {
14537             DisplayError(_("It is your turn"), 0);
14538             return;
14539         }
14540         cps = &first;
14541         break;
14542       case MachinePlaysBlack:
14543         if (WhiteOnMove(forwardMostMove)) {
14544             DisplayError(_("It is your turn"), 0);
14545             return;
14546         }
14547         cps = &first;
14548         break;
14549       case TwoMachinesPlay:
14550         if (WhiteOnMove(forwardMostMove) ==
14551             (first.twoMachinesColor[0] == 'w')) {
14552             cps = &first;
14553         } else {
14554             cps = &second;
14555         }
14556         break;
14557       case BeginningOfGame:
14558       default:
14559         return;
14560     }
14561     SendToProgram("?\n", cps);
14562 }
14563
14564 void
14565 TruncateGameEvent()
14566 {
14567     EditGameEvent();
14568     if (gameMode != EditGame) return;
14569     TruncateGame();
14570 }
14571
14572 void
14573 TruncateGame()
14574 {
14575     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14576     if (forwardMostMove > currentMove) {
14577         if (gameInfo.resultDetails != NULL) {
14578             free(gameInfo.resultDetails);
14579             gameInfo.resultDetails = NULL;
14580             gameInfo.result = GameUnfinished;
14581         }
14582         forwardMostMove = currentMove;
14583         HistorySet(parseList, backwardMostMove, forwardMostMove,
14584                    currentMove-1);
14585     }
14586 }
14587
14588 void
14589 HintEvent()
14590 {
14591     if (appData.noChessProgram) return;
14592     switch (gameMode) {
14593       case MachinePlaysWhite:
14594         if (WhiteOnMove(forwardMostMove)) {
14595             DisplayError(_("Wait until your turn"), 0);
14596             return;
14597         }
14598         break;
14599       case BeginningOfGame:
14600       case MachinePlaysBlack:
14601         if (!WhiteOnMove(forwardMostMove)) {
14602             DisplayError(_("Wait until your turn"), 0);
14603             return;
14604         }
14605         break;
14606       default:
14607         DisplayError(_("No hint available"), 0);
14608         return;
14609     }
14610     SendToProgram("hint\n", &first);
14611     hintRequested = TRUE;
14612 }
14613
14614 void
14615 BookEvent()
14616 {
14617     if (appData.noChessProgram) return;
14618     switch (gameMode) {
14619       case MachinePlaysWhite:
14620         if (WhiteOnMove(forwardMostMove)) {
14621             DisplayError(_("Wait until your turn"), 0);
14622             return;
14623         }
14624         break;
14625       case BeginningOfGame:
14626       case MachinePlaysBlack:
14627         if (!WhiteOnMove(forwardMostMove)) {
14628             DisplayError(_("Wait until your turn"), 0);
14629             return;
14630         }
14631         break;
14632       case EditPosition:
14633         EditPositionDone(TRUE);
14634         break;
14635       case TwoMachinesPlay:
14636         return;
14637       default:
14638         break;
14639     }
14640     SendToProgram("bk\n", &first);
14641     bookOutput[0] = NULLCHAR;
14642     bookRequested = TRUE;
14643 }
14644
14645 void
14646 AboutGameEvent()
14647 {
14648     char *tags = PGNTags(&gameInfo);
14649     TagsPopUp(tags, CmailMsg());
14650     free(tags);
14651 }
14652
14653 /* end button procedures */
14654
14655 void
14656 PrintPosition(fp, move)
14657      FILE *fp;
14658      int move;
14659 {
14660     int i, j;
14661
14662     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14663         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14664             char c = PieceToChar(boards[move][i][j]);
14665             fputc(c == 'x' ? '.' : c, fp);
14666             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14667         }
14668     }
14669     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14670       fprintf(fp, "white to play\n");
14671     else
14672       fprintf(fp, "black to play\n");
14673 }
14674
14675 void
14676 PrintOpponents(fp)
14677      FILE *fp;
14678 {
14679     if (gameInfo.white != NULL) {
14680         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14681     } else {
14682         fprintf(fp, "\n");
14683     }
14684 }
14685
14686 /* Find last component of program's own name, using some heuristics */
14687 void
14688 TidyProgramName(prog, host, buf)
14689      char *prog, *host, buf[MSG_SIZ];
14690 {
14691     char *p, *q;
14692     int local = (strcmp(host, "localhost") == 0);
14693     while (!local && (p = strchr(prog, ';')) != NULL) {
14694         p++;
14695         while (*p == ' ') p++;
14696         prog = p;
14697     }
14698     if (*prog == '"' || *prog == '\'') {
14699         q = strchr(prog + 1, *prog);
14700     } else {
14701         q = strchr(prog, ' ');
14702     }
14703     if (q == NULL) q = prog + strlen(prog);
14704     p = q;
14705     while (p >= prog && *p != '/' && *p != '\\') p--;
14706     p++;
14707     if(p == prog && *p == '"') p++;
14708     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14709     memcpy(buf, p, q - p);
14710     buf[q - p] = NULLCHAR;
14711     if (!local) {
14712         strcat(buf, "@");
14713         strcat(buf, host);
14714     }
14715 }
14716
14717 char *
14718 TimeControlTagValue()
14719 {
14720     char buf[MSG_SIZ];
14721     if (!appData.clockMode) {
14722       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14723     } else if (movesPerSession > 0) {
14724       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14725     } else if (timeIncrement == 0) {
14726       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14727     } else {
14728       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14729     }
14730     return StrSave(buf);
14731 }
14732
14733 void
14734 SetGameInfo()
14735 {
14736     /* This routine is used only for certain modes */
14737     VariantClass v = gameInfo.variant;
14738     ChessMove r = GameUnfinished;
14739     char *p = NULL;
14740
14741     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14742         r = gameInfo.result;
14743         p = gameInfo.resultDetails;
14744         gameInfo.resultDetails = NULL;
14745     }
14746     ClearGameInfo(&gameInfo);
14747     gameInfo.variant = v;
14748
14749     switch (gameMode) {
14750       case MachinePlaysWhite:
14751         gameInfo.event = StrSave( appData.pgnEventHeader );
14752         gameInfo.site = StrSave(HostName());
14753         gameInfo.date = PGNDate();
14754         gameInfo.round = StrSave("-");
14755         gameInfo.white = StrSave(first.tidy);
14756         gameInfo.black = StrSave(UserName());
14757         gameInfo.timeControl = TimeControlTagValue();
14758         break;
14759
14760       case MachinePlaysBlack:
14761         gameInfo.event = StrSave( appData.pgnEventHeader );
14762         gameInfo.site = StrSave(HostName());
14763         gameInfo.date = PGNDate();
14764         gameInfo.round = StrSave("-");
14765         gameInfo.white = StrSave(UserName());
14766         gameInfo.black = StrSave(first.tidy);
14767         gameInfo.timeControl = TimeControlTagValue();
14768         break;
14769
14770       case TwoMachinesPlay:
14771         gameInfo.event = StrSave( appData.pgnEventHeader );
14772         gameInfo.site = StrSave(HostName());
14773         gameInfo.date = PGNDate();
14774         if (roundNr > 0) {
14775             char buf[MSG_SIZ];
14776             snprintf(buf, MSG_SIZ, "%d", roundNr);
14777             gameInfo.round = StrSave(buf);
14778         } else {
14779             gameInfo.round = StrSave("-");
14780         }
14781         if (first.twoMachinesColor[0] == 'w') {
14782             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14783             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14784         } else {
14785             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14786             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14787         }
14788         gameInfo.timeControl = TimeControlTagValue();
14789         break;
14790
14791       case EditGame:
14792         gameInfo.event = StrSave("Edited game");
14793         gameInfo.site = StrSave(HostName());
14794         gameInfo.date = PGNDate();
14795         gameInfo.round = StrSave("-");
14796         gameInfo.white = StrSave("-");
14797         gameInfo.black = StrSave("-");
14798         gameInfo.result = r;
14799         gameInfo.resultDetails = p;
14800         break;
14801
14802       case EditPosition:
14803         gameInfo.event = StrSave("Edited position");
14804         gameInfo.site = StrSave(HostName());
14805         gameInfo.date = PGNDate();
14806         gameInfo.round = StrSave("-");
14807         gameInfo.white = StrSave("-");
14808         gameInfo.black = StrSave("-");
14809         break;
14810
14811       case IcsPlayingWhite:
14812       case IcsPlayingBlack:
14813       case IcsObserving:
14814       case IcsExamining:
14815         break;
14816
14817       case PlayFromGameFile:
14818         gameInfo.event = StrSave("Game from non-PGN file");
14819         gameInfo.site = StrSave(HostName());
14820         gameInfo.date = PGNDate();
14821         gameInfo.round = StrSave("-");
14822         gameInfo.white = StrSave("?");
14823         gameInfo.black = StrSave("?");
14824         break;
14825
14826       default:
14827         break;
14828     }
14829 }
14830
14831 void
14832 ReplaceComment(index, text)
14833      int index;
14834      char *text;
14835 {
14836     int len;
14837     char *p;
14838     float score;
14839
14840     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14841        pvInfoList[index-1].depth == len &&
14842        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14843        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14844     while (*text == '\n') text++;
14845     len = strlen(text);
14846     while (len > 0 && text[len - 1] == '\n') len--;
14847
14848     if (commentList[index] != NULL)
14849       free(commentList[index]);
14850
14851     if (len == 0) {
14852         commentList[index] = NULL;
14853         return;
14854     }
14855   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14856       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14857       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14858     commentList[index] = (char *) malloc(len + 2);
14859     strncpy(commentList[index], text, len);
14860     commentList[index][len] = '\n';
14861     commentList[index][len + 1] = NULLCHAR;
14862   } else {
14863     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14864     char *p;
14865     commentList[index] = (char *) malloc(len + 7);
14866     safeStrCpy(commentList[index], "{\n", 3);
14867     safeStrCpy(commentList[index]+2, text, len+1);
14868     commentList[index][len+2] = NULLCHAR;
14869     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14870     strcat(commentList[index], "\n}\n");
14871   }
14872 }
14873
14874 void
14875 CrushCRs(text)
14876      char *text;
14877 {
14878   char *p = text;
14879   char *q = text;
14880   char ch;
14881
14882   do {
14883     ch = *p++;
14884     if (ch == '\r') continue;
14885     *q++ = ch;
14886   } while (ch != '\0');
14887 }
14888
14889 void
14890 AppendComment(index, text, addBraces)
14891      int index;
14892      char *text;
14893      Boolean addBraces; // [HGM] braces: tells if we should add {}
14894 {
14895     int oldlen, len;
14896     char *old;
14897
14898 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14899     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14900
14901     CrushCRs(text);
14902     while (*text == '\n') text++;
14903     len = strlen(text);
14904     while (len > 0 && text[len - 1] == '\n') len--;
14905
14906     if (len == 0) return;
14907
14908     if (commentList[index] != NULL) {
14909       Boolean addClosingBrace = addBraces;
14910         old = commentList[index];
14911         oldlen = strlen(old);
14912         while(commentList[index][oldlen-1] ==  '\n')
14913           commentList[index][--oldlen] = NULLCHAR;
14914         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14915         safeStrCpy(commentList[index], old, oldlen + len + 6);
14916         free(old);
14917         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14918         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14919           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14920           while (*text == '\n') { text++; len--; }
14921           commentList[index][--oldlen] = NULLCHAR;
14922       }
14923         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14924         else          strcat(commentList[index], "\n");
14925         strcat(commentList[index], text);
14926         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14927         else          strcat(commentList[index], "\n");
14928     } else {
14929         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14930         if(addBraces)
14931           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14932         else commentList[index][0] = NULLCHAR;
14933         strcat(commentList[index], text);
14934         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14935         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14936     }
14937 }
14938
14939 static char * FindStr( char * text, char * sub_text )
14940 {
14941     char * result = strstr( text, sub_text );
14942
14943     if( result != NULL ) {
14944         result += strlen( sub_text );
14945     }
14946
14947     return result;
14948 }
14949
14950 /* [AS] Try to extract PV info from PGN comment */
14951 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14952 char *GetInfoFromComment( int index, char * text )
14953 {
14954     char * sep = text, *p;
14955
14956     if( text != NULL && index > 0 ) {
14957         int score = 0;
14958         int depth = 0;
14959         int time = -1, sec = 0, deci;
14960         char * s_eval = FindStr( text, "[%eval " );
14961         char * s_emt = FindStr( text, "[%emt " );
14962
14963         if( s_eval != NULL || s_emt != NULL ) {
14964             /* New style */
14965             char delim;
14966
14967             if( s_eval != NULL ) {
14968                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14969                     return text;
14970                 }
14971
14972                 if( delim != ']' ) {
14973                     return text;
14974                 }
14975             }
14976
14977             if( s_emt != NULL ) {
14978             }
14979                 return text;
14980         }
14981         else {
14982             /* We expect something like: [+|-]nnn.nn/dd */
14983             int score_lo = 0;
14984
14985             if(*text != '{') return text; // [HGM] braces: must be normal comment
14986
14987             sep = strchr( text, '/' );
14988             if( sep == NULL || sep < (text+4) ) {
14989                 return text;
14990             }
14991
14992             p = text;
14993             if(p[1] == '(') { // comment starts with PV
14994                p = strchr(p, ')'); // locate end of PV
14995                if(p == NULL || sep < p+5) return text;
14996                // at this point we have something like "{(.*) +0.23/6 ..."
14997                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14998                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14999                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15000             }
15001             time = -1; sec = -1; deci = -1;
15002             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15003                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15004                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15005                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15006                 return text;
15007             }
15008
15009             if( score_lo < 0 || score_lo >= 100 ) {
15010                 return text;
15011             }
15012
15013             if(sec >= 0) time = 600*time + 10*sec; else
15014             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15015
15016             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15017
15018             /* [HGM] PV time: now locate end of PV info */
15019             while( *++sep >= '0' && *sep <= '9'); // strip depth
15020             if(time >= 0)
15021             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15022             if(sec >= 0)
15023             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15024             if(deci >= 0)
15025             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15026             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15027         }
15028
15029         if( depth <= 0 ) {
15030             return text;
15031         }
15032
15033         if( time < 0 ) {
15034             time = -1;
15035         }
15036
15037         pvInfoList[index-1].depth = depth;
15038         pvInfoList[index-1].score = score;
15039         pvInfoList[index-1].time  = 10*time; // centi-sec
15040         if(*sep == '}') *sep = 0; else *--sep = '{';
15041         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15042     }
15043     return sep;
15044 }
15045
15046 void
15047 SendToProgram(message, cps)
15048      char *message;
15049      ChessProgramState *cps;
15050 {
15051     int count, outCount, error;
15052     char buf[MSG_SIZ];
15053
15054     if (cps->pr == NoProc) return;
15055     Attention(cps);
15056
15057     if (appData.debugMode) {
15058         TimeMark now;
15059         GetTimeMark(&now);
15060         fprintf(debugFP, "%ld >%-6s: %s",
15061                 SubtractTimeMarks(&now, &programStartTime),
15062                 cps->which, message);
15063     }
15064
15065     count = strlen(message);
15066     outCount = OutputToProcess(cps->pr, message, count, &error);
15067     if (outCount < count && !exiting
15068                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15069       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15070       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15071         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15072             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15073                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15074                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15075                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15076             } else {
15077                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15078                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15079                 gameInfo.result = res;
15080             }
15081             gameInfo.resultDetails = StrSave(buf);
15082         }
15083         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15084         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15085     }
15086 }
15087
15088 void
15089 ReceiveFromProgram(isr, closure, message, count, error)
15090      InputSourceRef isr;
15091      VOIDSTAR closure;
15092      char *message;
15093      int count;
15094      int error;
15095 {
15096     char *end_str;
15097     char buf[MSG_SIZ];
15098     ChessProgramState *cps = (ChessProgramState *)closure;
15099
15100     if (isr != cps->isr) return; /* Killed intentionally */
15101     if (count <= 0) {
15102         if (count == 0) {
15103             RemoveInputSource(cps->isr);
15104             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15105             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15106                     _(cps->which), cps->program);
15107         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15108                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15109                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15110                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15111                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15112                 } else {
15113                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15114                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15115                     gameInfo.result = res;
15116                 }
15117                 gameInfo.resultDetails = StrSave(buf);
15118             }
15119             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15120             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15121         } else {
15122             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15123                     _(cps->which), cps->program);
15124             RemoveInputSource(cps->isr);
15125
15126             /* [AS] Program is misbehaving badly... kill it */
15127             if( count == -2 ) {
15128                 DestroyChildProcess( cps->pr, 9 );
15129                 cps->pr = NoProc;
15130             }
15131
15132             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15133         }
15134         return;
15135     }
15136
15137     if ((end_str = strchr(message, '\r')) != NULL)
15138       *end_str = NULLCHAR;
15139     if ((end_str = strchr(message, '\n')) != NULL)
15140       *end_str = NULLCHAR;
15141
15142     if (appData.debugMode) {
15143         TimeMark now; int print = 1;
15144         char *quote = ""; char c; int i;
15145
15146         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15147                 char start = message[0];
15148                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15149                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15150                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15151                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15152                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15153                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15154                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15155                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15156                    sscanf(message, "hint: %c", &c)!=1 && 
15157                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15158                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15159                     print = (appData.engineComments >= 2);
15160                 }
15161                 message[0] = start; // restore original message
15162         }
15163         if(print) {
15164                 GetTimeMark(&now);
15165                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15166                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15167                         quote,
15168                         message);
15169         }
15170     }
15171
15172     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15173     if (appData.icsEngineAnalyze) {
15174         if (strstr(message, "whisper") != NULL ||
15175              strstr(message, "kibitz") != NULL ||
15176             strstr(message, "tellics") != NULL) return;
15177     }
15178
15179     HandleMachineMove(message, cps);
15180 }
15181
15182
15183 void
15184 SendTimeControl(cps, mps, tc, inc, sd, st)
15185      ChessProgramState *cps;
15186      int mps, inc, sd, st;
15187      long tc;
15188 {
15189     char buf[MSG_SIZ];
15190     int seconds;
15191
15192     if( timeControl_2 > 0 ) {
15193         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15194             tc = timeControl_2;
15195         }
15196     }
15197     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15198     inc /= cps->timeOdds;
15199     st  /= cps->timeOdds;
15200
15201     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15202
15203     if (st > 0) {
15204       /* Set exact time per move, normally using st command */
15205       if (cps->stKludge) {
15206         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15207         seconds = st % 60;
15208         if (seconds == 0) {
15209           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15210         } else {
15211           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15212         }
15213       } else {
15214         snprintf(buf, MSG_SIZ, "st %d\n", st);
15215       }
15216     } else {
15217       /* Set conventional or incremental time control, using level command */
15218       if (seconds == 0) {
15219         /* Note old gnuchess bug -- minutes:seconds used to not work.
15220            Fixed in later versions, but still avoid :seconds
15221            when seconds is 0. */
15222         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15223       } else {
15224         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15225                  seconds, inc/1000.);
15226       }
15227     }
15228     SendToProgram(buf, cps);
15229
15230     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15231     /* Orthogonally, limit search to given depth */
15232     if (sd > 0) {
15233       if (cps->sdKludge) {
15234         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15235       } else {
15236         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15237       }
15238       SendToProgram(buf, cps);
15239     }
15240
15241     if(cps->nps >= 0) { /* [HGM] nps */
15242         if(cps->supportsNPS == FALSE)
15243           cps->nps = -1; // don't use if engine explicitly says not supported!
15244         else {
15245           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15246           SendToProgram(buf, cps);
15247         }
15248     }
15249 }
15250
15251 ChessProgramState *WhitePlayer()
15252 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15253 {
15254     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15255        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15256         return &second;
15257     return &first;
15258 }
15259
15260 void
15261 SendTimeRemaining(cps, machineWhite)
15262      ChessProgramState *cps;
15263      int /*boolean*/ machineWhite;
15264 {
15265     char message[MSG_SIZ];
15266     long time, otime;
15267
15268     /* Note: this routine must be called when the clocks are stopped
15269        or when they have *just* been set or switched; otherwise
15270        it will be off by the time since the current tick started.
15271     */
15272     if (machineWhite) {
15273         time = whiteTimeRemaining / 10;
15274         otime = blackTimeRemaining / 10;
15275     } else {
15276         time = blackTimeRemaining / 10;
15277         otime = whiteTimeRemaining / 10;
15278     }
15279     /* [HGM] translate opponent's time by time-odds factor */
15280     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15281     if (appData.debugMode) {
15282         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15283     }
15284
15285     if (time <= 0) time = 1;
15286     if (otime <= 0) otime = 1;
15287
15288     snprintf(message, MSG_SIZ, "time %ld\n", time);
15289     SendToProgram(message, cps);
15290
15291     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15292     SendToProgram(message, cps);
15293 }
15294
15295 int
15296 BoolFeature(p, name, loc, cps)
15297      char **p;
15298      char *name;
15299      int *loc;
15300      ChessProgramState *cps;
15301 {
15302   char buf[MSG_SIZ];
15303   int len = strlen(name);
15304   int val;
15305
15306   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15307     (*p) += len + 1;
15308     sscanf(*p, "%d", &val);
15309     *loc = (val != 0);
15310     while (**p && **p != ' ')
15311       (*p)++;
15312     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15313     SendToProgram(buf, cps);
15314     return TRUE;
15315   }
15316   return FALSE;
15317 }
15318
15319 int
15320 IntFeature(p, name, loc, cps)
15321      char **p;
15322      char *name;
15323      int *loc;
15324      ChessProgramState *cps;
15325 {
15326   char buf[MSG_SIZ];
15327   int len = strlen(name);
15328   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15329     (*p) += len + 1;
15330     sscanf(*p, "%d", loc);
15331     while (**p && **p != ' ') (*p)++;
15332     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15333     SendToProgram(buf, cps);
15334     return TRUE;
15335   }
15336   return FALSE;
15337 }
15338
15339 int
15340 StringFeature(p, name, loc, cps)
15341      char **p;
15342      char *name;
15343      char loc[];
15344      ChessProgramState *cps;
15345 {
15346   char buf[MSG_SIZ];
15347   int len = strlen(name);
15348   if (strncmp((*p), name, len) == 0
15349       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15350     (*p) += len + 2;
15351     sscanf(*p, "%[^\"]", loc);
15352     while (**p && **p != '\"') (*p)++;
15353     if (**p == '\"') (*p)++;
15354     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15355     SendToProgram(buf, cps);
15356     return TRUE;
15357   }
15358   return FALSE;
15359 }
15360
15361 int
15362 ParseOption(Option *opt, ChessProgramState *cps)
15363 // [HGM] options: process the string that defines an engine option, and determine
15364 // name, type, default value, and allowed value range
15365 {
15366         char *p, *q, buf[MSG_SIZ];
15367         int n, min = (-1)<<31, max = 1<<31, def;
15368
15369         if(p = strstr(opt->name, " -spin ")) {
15370             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15371             if(max < min) max = min; // enforce consistency
15372             if(def < min) def = min;
15373             if(def > max) def = max;
15374             opt->value = def;
15375             opt->min = min;
15376             opt->max = max;
15377             opt->type = Spin;
15378         } else if((p = strstr(opt->name, " -slider "))) {
15379             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15380             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15381             if(max < min) max = min; // enforce consistency
15382             if(def < min) def = min;
15383             if(def > max) def = max;
15384             opt->value = def;
15385             opt->min = min;
15386             opt->max = max;
15387             opt->type = Spin; // Slider;
15388         } else if((p = strstr(opt->name, " -string "))) {
15389             opt->textValue = p+9;
15390             opt->type = TextBox;
15391         } else if((p = strstr(opt->name, " -file "))) {
15392             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15393             opt->textValue = p+7;
15394             opt->type = FileName; // FileName;
15395         } else if((p = strstr(opt->name, " -path "))) {
15396             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15397             opt->textValue = p+7;
15398             opt->type = PathName; // PathName;
15399         } else if(p = strstr(opt->name, " -check ")) {
15400             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15401             opt->value = (def != 0);
15402             opt->type = CheckBox;
15403         } else if(p = strstr(opt->name, " -combo ")) {
15404             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15405             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15406             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15407             opt->value = n = 0;
15408             while(q = StrStr(q, " /// ")) {
15409                 n++; *q = 0;    // count choices, and null-terminate each of them
15410                 q += 5;
15411                 if(*q == '*') { // remember default, which is marked with * prefix
15412                     q++;
15413                     opt->value = n;
15414                 }
15415                 cps->comboList[cps->comboCnt++] = q;
15416             }
15417             cps->comboList[cps->comboCnt++] = NULL;
15418             opt->max = n + 1;
15419             opt->type = ComboBox;
15420         } else if(p = strstr(opt->name, " -button")) {
15421             opt->type = Button;
15422         } else if(p = strstr(opt->name, " -save")) {
15423             opt->type = SaveButton;
15424         } else return FALSE;
15425         *p = 0; // terminate option name
15426         // now look if the command-line options define a setting for this engine option.
15427         if(cps->optionSettings && cps->optionSettings[0])
15428             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15429         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15430           snprintf(buf, MSG_SIZ, "option %s", p);
15431                 if(p = strstr(buf, ",")) *p = 0;
15432                 if(q = strchr(buf, '=')) switch(opt->type) {
15433                     case ComboBox:
15434                         for(n=0; n<opt->max; n++)
15435                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15436                         break;
15437                     case TextBox:
15438                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15439                         break;
15440                     case Spin:
15441                     case CheckBox:
15442                         opt->value = atoi(q+1);
15443                     default:
15444                         break;
15445                 }
15446                 strcat(buf, "\n");
15447                 SendToProgram(buf, cps);
15448         }
15449         return TRUE;
15450 }
15451
15452 void
15453 FeatureDone(cps, val)
15454      ChessProgramState* cps;
15455      int val;
15456 {
15457   DelayedEventCallback cb = GetDelayedEvent();
15458   if ((cb == InitBackEnd3 && cps == &first) ||
15459       (cb == SettingsMenuIfReady && cps == &second) ||
15460       (cb == LoadEngine) ||
15461       (cb == TwoMachinesEventIfReady)) {
15462     CancelDelayedEvent();
15463     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15464   }
15465   cps->initDone = val;
15466 }
15467
15468 /* Parse feature command from engine */
15469 void
15470 ParseFeatures(args, cps)
15471      char* args;
15472      ChessProgramState *cps;
15473 {
15474   char *p = args;
15475   char *q;
15476   int val;
15477   char buf[MSG_SIZ];
15478
15479   for (;;) {
15480     while (*p == ' ') p++;
15481     if (*p == NULLCHAR) return;
15482
15483     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15484     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15485     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15486     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15487     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15488     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15489     if (BoolFeature(&p, "reuse", &val, cps)) {
15490       /* Engine can disable reuse, but can't enable it if user said no */
15491       if (!val) cps->reuse = FALSE;
15492       continue;
15493     }
15494     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15495     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15496       if (gameMode == TwoMachinesPlay) {
15497         DisplayTwoMachinesTitle();
15498       } else {
15499         DisplayTitle("");
15500       }
15501       continue;
15502     }
15503     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15504     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15505     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15506     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15507     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15508     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15509     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15510     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15511     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15512     if (IntFeature(&p, "done", &val, cps)) {
15513       FeatureDone(cps, val);
15514       continue;
15515     }
15516     /* Added by Tord: */
15517     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15518     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15519     /* End of additions by Tord */
15520
15521     /* [HGM] added features: */
15522     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15523     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15524     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15525     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15526     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15527     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15528     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15529         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15530           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15531             SendToProgram(buf, cps);
15532             continue;
15533         }
15534         if(cps->nrOptions >= MAX_OPTIONS) {
15535             cps->nrOptions--;
15536             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15537             DisplayError(buf, 0);
15538         }
15539         continue;
15540     }
15541     /* End of additions by HGM */
15542
15543     /* unknown feature: complain and skip */
15544     q = p;
15545     while (*q && *q != '=') q++;
15546     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15547     SendToProgram(buf, cps);
15548     p = q;
15549     if (*p == '=') {
15550       p++;
15551       if (*p == '\"') {
15552         p++;
15553         while (*p && *p != '\"') p++;
15554         if (*p == '\"') p++;
15555       } else {
15556         while (*p && *p != ' ') p++;
15557       }
15558     }
15559   }
15560
15561 }
15562
15563 void
15564 PeriodicUpdatesEvent(newState)
15565      int newState;
15566 {
15567     if (newState == appData.periodicUpdates)
15568       return;
15569
15570     appData.periodicUpdates=newState;
15571
15572     /* Display type changes, so update it now */
15573 //    DisplayAnalysis();
15574
15575     /* Get the ball rolling again... */
15576     if (newState) {
15577         AnalysisPeriodicEvent(1);
15578         StartAnalysisClock();
15579     }
15580 }
15581
15582 void
15583 PonderNextMoveEvent(newState)
15584      int newState;
15585 {
15586     if (newState == appData.ponderNextMove) return;
15587     if (gameMode == EditPosition) EditPositionDone(TRUE);
15588     if (newState) {
15589         SendToProgram("hard\n", &first);
15590         if (gameMode == TwoMachinesPlay) {
15591             SendToProgram("hard\n", &second);
15592         }
15593     } else {
15594         SendToProgram("easy\n", &first);
15595         thinkOutput[0] = NULLCHAR;
15596         if (gameMode == TwoMachinesPlay) {
15597             SendToProgram("easy\n", &second);
15598         }
15599     }
15600     appData.ponderNextMove = newState;
15601 }
15602
15603 void
15604 NewSettingEvent(option, feature, command, value)
15605      char *command;
15606      int option, value, *feature;
15607 {
15608     char buf[MSG_SIZ];
15609
15610     if (gameMode == EditPosition) EditPositionDone(TRUE);
15611     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15612     if(feature == NULL || *feature) SendToProgram(buf, &first);
15613     if (gameMode == TwoMachinesPlay) {
15614         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15615     }
15616 }
15617
15618 void
15619 ShowThinkingEvent()
15620 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15621 {
15622     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15623     int newState = appData.showThinking
15624         // [HGM] thinking: other features now need thinking output as well
15625         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15626
15627     if (oldState == newState) return;
15628     oldState = newState;
15629     if (gameMode == EditPosition) EditPositionDone(TRUE);
15630     if (oldState) {
15631         SendToProgram("post\n", &first);
15632         if (gameMode == TwoMachinesPlay) {
15633             SendToProgram("post\n", &second);
15634         }
15635     } else {
15636         SendToProgram("nopost\n", &first);
15637         thinkOutput[0] = NULLCHAR;
15638         if (gameMode == TwoMachinesPlay) {
15639             SendToProgram("nopost\n", &second);
15640         }
15641     }
15642 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15643 }
15644
15645 void
15646 AskQuestionEvent(title, question, replyPrefix, which)
15647      char *title; char *question; char *replyPrefix; char *which;
15648 {
15649   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15650   if (pr == NoProc) return;
15651   AskQuestion(title, question, replyPrefix, pr);
15652 }
15653
15654 void
15655 TypeInEvent(char firstChar)
15656 {
15657     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15658         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15659         gameMode == AnalyzeMode || gameMode == EditGame || 
15660         gameMode == EditPosition || gameMode == IcsExamining ||
15661         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15662         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15663                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15664                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15665         gameMode == Training) PopUpMoveDialog(firstChar);
15666 }
15667
15668 void
15669 TypeInDoneEvent(char *move)
15670 {
15671         Board board;
15672         int n, fromX, fromY, toX, toY;
15673         char promoChar;
15674         ChessMove moveType;
15675
15676         // [HGM] FENedit
15677         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15678                 EditPositionPasteFEN(move);
15679                 return;
15680         }
15681         // [HGM] movenum: allow move number to be typed in any mode
15682         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15683           ToNrEvent(2*n-1);
15684           return;
15685         }
15686
15687       if (gameMode != EditGame && currentMove != forwardMostMove && 
15688         gameMode != Training) {
15689         DisplayMoveError(_("Displayed move is not current"));
15690       } else {
15691         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15692           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15693         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15694         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15695           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15696           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15697         } else {
15698           DisplayMoveError(_("Could not parse move"));
15699         }
15700       }
15701 }
15702
15703 void
15704 DisplayMove(moveNumber)
15705      int moveNumber;
15706 {
15707     char message[MSG_SIZ];
15708     char res[MSG_SIZ];
15709     char cpThinkOutput[MSG_SIZ];
15710
15711     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15712
15713     if (moveNumber == forwardMostMove - 1 ||
15714         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15715
15716         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15717
15718         if (strchr(cpThinkOutput, '\n')) {
15719             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15720         }
15721     } else {
15722         *cpThinkOutput = NULLCHAR;
15723     }
15724
15725     /* [AS] Hide thinking from human user */
15726     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15727         *cpThinkOutput = NULLCHAR;
15728         if( thinkOutput[0] != NULLCHAR ) {
15729             int i;
15730
15731             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15732                 cpThinkOutput[i] = '.';
15733             }
15734             cpThinkOutput[i] = NULLCHAR;
15735             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15736         }
15737     }
15738
15739     if (moveNumber == forwardMostMove - 1 &&
15740         gameInfo.resultDetails != NULL) {
15741         if (gameInfo.resultDetails[0] == NULLCHAR) {
15742           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15743         } else {
15744           snprintf(res, MSG_SIZ, " {%s} %s",
15745                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15746         }
15747     } else {
15748         res[0] = NULLCHAR;
15749     }
15750
15751     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15752         DisplayMessage(res, cpThinkOutput);
15753     } else {
15754       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15755                 WhiteOnMove(moveNumber) ? " " : ".. ",
15756                 parseList[moveNumber], res);
15757         DisplayMessage(message, cpThinkOutput);
15758     }
15759 }
15760
15761 void
15762 DisplayComment(moveNumber, text)
15763      int moveNumber;
15764      char *text;
15765 {
15766     char title[MSG_SIZ];
15767
15768     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15769       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15770     } else {
15771       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15772               WhiteOnMove(moveNumber) ? " " : ".. ",
15773               parseList[moveNumber]);
15774     }
15775     if (text != NULL && (appData.autoDisplayComment || commentUp))
15776         CommentPopUp(title, text);
15777 }
15778
15779 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15780  * might be busy thinking or pondering.  It can be omitted if your
15781  * gnuchess is configured to stop thinking immediately on any user
15782  * input.  However, that gnuchess feature depends on the FIONREAD
15783  * ioctl, which does not work properly on some flavors of Unix.
15784  */
15785 void
15786 Attention(cps)
15787      ChessProgramState *cps;
15788 {
15789 #if ATTENTION
15790     if (!cps->useSigint) return;
15791     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15792     switch (gameMode) {
15793       case MachinePlaysWhite:
15794       case MachinePlaysBlack:
15795       case TwoMachinesPlay:
15796       case IcsPlayingWhite:
15797       case IcsPlayingBlack:
15798       case AnalyzeMode:
15799       case AnalyzeFile:
15800         /* Skip if we know it isn't thinking */
15801         if (!cps->maybeThinking) return;
15802         if (appData.debugMode)
15803           fprintf(debugFP, "Interrupting %s\n", cps->which);
15804         InterruptChildProcess(cps->pr);
15805         cps->maybeThinking = FALSE;
15806         break;
15807       default:
15808         break;
15809     }
15810 #endif /*ATTENTION*/
15811 }
15812
15813 int
15814 CheckFlags()
15815 {
15816     if (whiteTimeRemaining <= 0) {
15817         if (!whiteFlag) {
15818             whiteFlag = TRUE;
15819             if (appData.icsActive) {
15820                 if (appData.autoCallFlag &&
15821                     gameMode == IcsPlayingBlack && !blackFlag) {
15822                   SendToICS(ics_prefix);
15823                   SendToICS("flag\n");
15824                 }
15825             } else {
15826                 if (blackFlag) {
15827                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15828                 } else {
15829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15830                     if (appData.autoCallFlag) {
15831                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15832                         return TRUE;
15833                     }
15834                 }
15835             }
15836         }
15837     }
15838     if (blackTimeRemaining <= 0) {
15839         if (!blackFlag) {
15840             blackFlag = TRUE;
15841             if (appData.icsActive) {
15842                 if (appData.autoCallFlag &&
15843                     gameMode == IcsPlayingWhite && !whiteFlag) {
15844                   SendToICS(ics_prefix);
15845                   SendToICS("flag\n");
15846                 }
15847             } else {
15848                 if (whiteFlag) {
15849                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15850                 } else {
15851                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15852                     if (appData.autoCallFlag) {
15853                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15854                         return TRUE;
15855                     }
15856                 }
15857             }
15858         }
15859     }
15860     return FALSE;
15861 }
15862
15863 void
15864 CheckTimeControl()
15865 {
15866     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15867         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15868
15869     /*
15870      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15871      */
15872     if ( !WhiteOnMove(forwardMostMove) ) {
15873         /* White made time control */
15874         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15875         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15876         /* [HGM] time odds: correct new time quota for time odds! */
15877                                             / WhitePlayer()->timeOdds;
15878         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15879     } else {
15880         lastBlack -= blackTimeRemaining;
15881         /* Black made time control */
15882         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15883                                             / WhitePlayer()->other->timeOdds;
15884         lastWhite = whiteTimeRemaining;
15885     }
15886 }
15887
15888 void
15889 DisplayBothClocks()
15890 {
15891     int wom = gameMode == EditPosition ?
15892       !blackPlaysFirst : WhiteOnMove(currentMove);
15893     DisplayWhiteClock(whiteTimeRemaining, wom);
15894     DisplayBlackClock(blackTimeRemaining, !wom);
15895 }
15896
15897
15898 /* Timekeeping seems to be a portability nightmare.  I think everyone
15899    has ftime(), but I'm really not sure, so I'm including some ifdefs
15900    to use other calls if you don't.  Clocks will be less accurate if
15901    you have neither ftime nor gettimeofday.
15902 */
15903
15904 /* VS 2008 requires the #include outside of the function */
15905 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15906 #include <sys/timeb.h>
15907 #endif
15908
15909 /* Get the current time as a TimeMark */
15910 void
15911 GetTimeMark(tm)
15912      TimeMark *tm;
15913 {
15914 #if HAVE_GETTIMEOFDAY
15915
15916     struct timeval timeVal;
15917     struct timezone timeZone;
15918
15919     gettimeofday(&timeVal, &timeZone);
15920     tm->sec = (long) timeVal.tv_sec;
15921     tm->ms = (int) (timeVal.tv_usec / 1000L);
15922
15923 #else /*!HAVE_GETTIMEOFDAY*/
15924 #if HAVE_FTIME
15925
15926 // include <sys/timeb.h> / moved to just above start of function
15927     struct timeb timeB;
15928
15929     ftime(&timeB);
15930     tm->sec = (long) timeB.time;
15931     tm->ms = (int) timeB.millitm;
15932
15933 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15934     tm->sec = (long) time(NULL);
15935     tm->ms = 0;
15936 #endif
15937 #endif
15938 }
15939
15940 /* Return the difference in milliseconds between two
15941    time marks.  We assume the difference will fit in a long!
15942 */
15943 long
15944 SubtractTimeMarks(tm2, tm1)
15945      TimeMark *tm2, *tm1;
15946 {
15947     return 1000L*(tm2->sec - tm1->sec) +
15948            (long) (tm2->ms - tm1->ms);
15949 }
15950
15951
15952 /*
15953  * Code to manage the game clocks.
15954  *
15955  * In tournament play, black starts the clock and then white makes a move.
15956  * We give the human user a slight advantage if he is playing white---the
15957  * clocks don't run until he makes his first move, so it takes zero time.
15958  * Also, we don't account for network lag, so we could get out of sync
15959  * with GNU Chess's clock -- but then, referees are always right.
15960  */
15961
15962 static TimeMark tickStartTM;
15963 static long intendedTickLength;
15964
15965 long
15966 NextTickLength(timeRemaining)
15967      long timeRemaining;
15968 {
15969     long nominalTickLength, nextTickLength;
15970
15971     if (timeRemaining > 0L && timeRemaining <= 10000L)
15972       nominalTickLength = 100L;
15973     else
15974       nominalTickLength = 1000L;
15975     nextTickLength = timeRemaining % nominalTickLength;
15976     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15977
15978     return nextTickLength;
15979 }
15980
15981 /* Adjust clock one minute up or down */
15982 void
15983 AdjustClock(Boolean which, int dir)
15984 {
15985     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15986     if(which) blackTimeRemaining += 60000*dir;
15987     else      whiteTimeRemaining += 60000*dir;
15988     DisplayBothClocks();
15989     adjustedClock = TRUE;
15990 }
15991
15992 /* Stop clocks and reset to a fresh time control */
15993 void
15994 ResetClocks()
15995 {
15996     (void) StopClockTimer();
15997     if (appData.icsActive) {
15998         whiteTimeRemaining = blackTimeRemaining = 0;
15999     } else if (searchTime) {
16000         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16001         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16002     } else { /* [HGM] correct new time quote for time odds */
16003         whiteTC = blackTC = fullTimeControlString;
16004         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16005         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16006     }
16007     if (whiteFlag || blackFlag) {
16008         DisplayTitle("");
16009         whiteFlag = blackFlag = FALSE;
16010     }
16011     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16012     DisplayBothClocks();
16013     adjustedClock = FALSE;
16014 }
16015
16016 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16017
16018 /* Decrement running clock by amount of time that has passed */
16019 void
16020 DecrementClocks()
16021 {
16022     long timeRemaining;
16023     long lastTickLength, fudge;
16024     TimeMark now;
16025
16026     if (!appData.clockMode) return;
16027     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16028
16029     GetTimeMark(&now);
16030
16031     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16032
16033     /* Fudge if we woke up a little too soon */
16034     fudge = intendedTickLength - lastTickLength;
16035     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16036
16037     if (WhiteOnMove(forwardMostMove)) {
16038         if(whiteNPS >= 0) lastTickLength = 0;
16039         timeRemaining = whiteTimeRemaining -= lastTickLength;
16040         if(timeRemaining < 0 && !appData.icsActive) {
16041             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16042             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16043                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16044                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16045             }
16046         }
16047         DisplayWhiteClock(whiteTimeRemaining - fudge,
16048                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16049     } else {
16050         if(blackNPS >= 0) lastTickLength = 0;
16051         timeRemaining = blackTimeRemaining -= lastTickLength;
16052         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16053             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16054             if(suddenDeath) {
16055                 blackStartMove = forwardMostMove;
16056                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16057             }
16058         }
16059         DisplayBlackClock(blackTimeRemaining - fudge,
16060                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16061     }
16062     if (CheckFlags()) return;
16063
16064     tickStartTM = now;
16065     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16066     StartClockTimer(intendedTickLength);
16067
16068     /* if the time remaining has fallen below the alarm threshold, sound the
16069      * alarm. if the alarm has sounded and (due to a takeback or time control
16070      * with increment) the time remaining has increased to a level above the
16071      * threshold, reset the alarm so it can sound again.
16072      */
16073
16074     if (appData.icsActive && appData.icsAlarm) {
16075
16076         /* make sure we are dealing with the user's clock */
16077         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16078                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16079            )) return;
16080
16081         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16082             alarmSounded = FALSE;
16083         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16084             PlayAlarmSound();
16085             alarmSounded = TRUE;
16086         }
16087     }
16088 }
16089
16090
16091 /* A player has just moved, so stop the previously running
16092    clock and (if in clock mode) start the other one.
16093    We redisplay both clocks in case we're in ICS mode, because
16094    ICS gives us an update to both clocks after every move.
16095    Note that this routine is called *after* forwardMostMove
16096    is updated, so the last fractional tick must be subtracted
16097    from the color that is *not* on move now.
16098 */
16099 void
16100 SwitchClocks(int newMoveNr)
16101 {
16102     long lastTickLength;
16103     TimeMark now;
16104     int flagged = FALSE;
16105
16106     GetTimeMark(&now);
16107
16108     if (StopClockTimer() && appData.clockMode) {
16109         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16110         if (!WhiteOnMove(forwardMostMove)) {
16111             if(blackNPS >= 0) lastTickLength = 0;
16112             blackTimeRemaining -= lastTickLength;
16113            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16114 //         if(pvInfoList[forwardMostMove].time == -1)
16115                  pvInfoList[forwardMostMove].time =               // use GUI time
16116                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16117         } else {
16118            if(whiteNPS >= 0) lastTickLength = 0;
16119            whiteTimeRemaining -= lastTickLength;
16120            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16121 //         if(pvInfoList[forwardMostMove].time == -1)
16122                  pvInfoList[forwardMostMove].time =
16123                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16124         }
16125         flagged = CheckFlags();
16126     }
16127     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16128     CheckTimeControl();
16129
16130     if (flagged || !appData.clockMode) return;
16131
16132     switch (gameMode) {
16133       case MachinePlaysBlack:
16134       case MachinePlaysWhite:
16135       case BeginningOfGame:
16136         if (pausing) return;
16137         break;
16138
16139       case EditGame:
16140       case PlayFromGameFile:
16141       case IcsExamining:
16142         return;
16143
16144       default:
16145         break;
16146     }
16147
16148     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16149         if(WhiteOnMove(forwardMostMove))
16150              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16151         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16152     }
16153
16154     tickStartTM = now;
16155     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16156       whiteTimeRemaining : blackTimeRemaining);
16157     StartClockTimer(intendedTickLength);
16158 }
16159
16160
16161 /* Stop both clocks */
16162 void
16163 StopClocks()
16164 {
16165     long lastTickLength;
16166     TimeMark now;
16167
16168     if (!StopClockTimer()) return;
16169     if (!appData.clockMode) return;
16170
16171     GetTimeMark(&now);
16172
16173     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16174     if (WhiteOnMove(forwardMostMove)) {
16175         if(whiteNPS >= 0) lastTickLength = 0;
16176         whiteTimeRemaining -= lastTickLength;
16177         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16178     } else {
16179         if(blackNPS >= 0) lastTickLength = 0;
16180         blackTimeRemaining -= lastTickLength;
16181         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16182     }
16183     CheckFlags();
16184 }
16185
16186 /* Start clock of player on move.  Time may have been reset, so
16187    if clock is already running, stop and restart it. */
16188 void
16189 StartClocks()
16190 {
16191     (void) StopClockTimer(); /* in case it was running already */
16192     DisplayBothClocks();
16193     if (CheckFlags()) return;
16194
16195     if (!appData.clockMode) return;
16196     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16197
16198     GetTimeMark(&tickStartTM);
16199     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16200       whiteTimeRemaining : blackTimeRemaining);
16201
16202    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16203     whiteNPS = blackNPS = -1;
16204     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16205        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16206         whiteNPS = first.nps;
16207     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16208        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16209         blackNPS = first.nps;
16210     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16211         whiteNPS = second.nps;
16212     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16213         blackNPS = second.nps;
16214     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16215
16216     StartClockTimer(intendedTickLength);
16217 }
16218
16219 char *
16220 TimeString(ms)
16221      long ms;
16222 {
16223     long second, minute, hour, day;
16224     char *sign = "";
16225     static char buf[32];
16226
16227     if (ms > 0 && ms <= 9900) {
16228       /* convert milliseconds to tenths, rounding up */
16229       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16230
16231       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16232       return buf;
16233     }
16234
16235     /* convert milliseconds to seconds, rounding up */
16236     /* use floating point to avoid strangeness of integer division
16237        with negative dividends on many machines */
16238     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16239
16240     if (second < 0) {
16241         sign = "-";
16242         second = -second;
16243     }
16244
16245     day = second / (60 * 60 * 24);
16246     second = second % (60 * 60 * 24);
16247     hour = second / (60 * 60);
16248     second = second % (60 * 60);
16249     minute = second / 60;
16250     second = second % 60;
16251
16252     if (day > 0)
16253       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16254               sign, day, hour, minute, second);
16255     else if (hour > 0)
16256       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16257     else
16258       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16259
16260     return buf;
16261 }
16262
16263
16264 /*
16265  * This is necessary because some C libraries aren't ANSI C compliant yet.
16266  */
16267 char *
16268 StrStr(string, match)
16269      char *string, *match;
16270 {
16271     int i, length;
16272
16273     length = strlen(match);
16274
16275     for (i = strlen(string) - length; i >= 0; i--, string++)
16276       if (!strncmp(match, string, length))
16277         return string;
16278
16279     return NULL;
16280 }
16281
16282 char *
16283 StrCaseStr(string, match)
16284      char *string, *match;
16285 {
16286     int i, j, length;
16287
16288     length = strlen(match);
16289
16290     for (i = strlen(string) - length; i >= 0; i--, string++) {
16291         for (j = 0; j < length; j++) {
16292             if (ToLower(match[j]) != ToLower(string[j]))
16293               break;
16294         }
16295         if (j == length) return string;
16296     }
16297
16298     return NULL;
16299 }
16300
16301 #ifndef _amigados
16302 int
16303 StrCaseCmp(s1, s2)
16304      char *s1, *s2;
16305 {
16306     char c1, c2;
16307
16308     for (;;) {
16309         c1 = ToLower(*s1++);
16310         c2 = ToLower(*s2++);
16311         if (c1 > c2) return 1;
16312         if (c1 < c2) return -1;
16313         if (c1 == NULLCHAR) return 0;
16314     }
16315 }
16316
16317
16318 int
16319 ToLower(c)
16320      int c;
16321 {
16322     return isupper(c) ? tolower(c) : c;
16323 }
16324
16325
16326 int
16327 ToUpper(c)
16328      int c;
16329 {
16330     return islower(c) ? toupper(c) : c;
16331 }
16332 #endif /* !_amigados    */
16333
16334 char *
16335 StrSave(s)
16336      char *s;
16337 {
16338   char *ret;
16339
16340   if ((ret = (char *) malloc(strlen(s) + 1)))
16341     {
16342       safeStrCpy(ret, s, strlen(s)+1);
16343     }
16344   return ret;
16345 }
16346
16347 char *
16348 StrSavePtr(s, savePtr)
16349      char *s, **savePtr;
16350 {
16351     if (*savePtr) {
16352         free(*savePtr);
16353     }
16354     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16355       safeStrCpy(*savePtr, s, strlen(s)+1);
16356     }
16357     return(*savePtr);
16358 }
16359
16360 char *
16361 PGNDate()
16362 {
16363     time_t clock;
16364     struct tm *tm;
16365     char buf[MSG_SIZ];
16366
16367     clock = time((time_t *)NULL);
16368     tm = localtime(&clock);
16369     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16370             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16371     return StrSave(buf);
16372 }
16373
16374
16375 char *
16376 PositionToFEN(move, overrideCastling)
16377      int move;
16378      char *overrideCastling;
16379 {
16380     int i, j, fromX, fromY, toX, toY;
16381     int whiteToPlay;
16382     char buf[MSG_SIZ];
16383     char *p, *q;
16384     int emptycount;
16385     ChessSquare piece;
16386
16387     whiteToPlay = (gameMode == EditPosition) ?
16388       !blackPlaysFirst : (move % 2 == 0);
16389     p = buf;
16390
16391     /* Piece placement data */
16392     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16393         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16394         emptycount = 0;
16395         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16396             if (boards[move][i][j] == EmptySquare) {
16397                 emptycount++;
16398             } else { ChessSquare piece = boards[move][i][j];
16399                 if (emptycount > 0) {
16400                     if(emptycount<10) /* [HGM] can be >= 10 */
16401                         *p++ = '0' + emptycount;
16402                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16403                     emptycount = 0;
16404                 }
16405                 if(PieceToChar(piece) == '+') {
16406                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16407                     *p++ = '+';
16408                     piece = (ChessSquare)(DEMOTED piece);
16409                 }
16410                 *p++ = PieceToChar(piece);
16411                 if(p[-1] == '~') {
16412                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16413                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16414                     *p++ = '~';
16415                 }
16416             }
16417         }
16418         if (emptycount > 0) {
16419             if(emptycount<10) /* [HGM] can be >= 10 */
16420                 *p++ = '0' + emptycount;
16421             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16422             emptycount = 0;
16423         }
16424         *p++ = '/';
16425     }
16426     *(p - 1) = ' ';
16427
16428     /* [HGM] print Crazyhouse or Shogi holdings */
16429     if( gameInfo.holdingsWidth ) {
16430         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16431         q = p;
16432         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16433             piece = boards[move][i][BOARD_WIDTH-1];
16434             if( piece != EmptySquare )
16435               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16436                   *p++ = PieceToChar(piece);
16437         }
16438         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16439             piece = boards[move][BOARD_HEIGHT-i-1][0];
16440             if( piece != EmptySquare )
16441               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16442                   *p++ = PieceToChar(piece);
16443         }
16444
16445         if( q == p ) *p++ = '-';
16446         *p++ = ']';
16447         *p++ = ' ';
16448     }
16449
16450     /* Active color */
16451     *p++ = whiteToPlay ? 'w' : 'b';
16452     *p++ = ' ';
16453
16454   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16455     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16456   } else {
16457   if(nrCastlingRights) {
16458      q = p;
16459      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16460        /* [HGM] write directly from rights */
16461            if(boards[move][CASTLING][2] != NoRights &&
16462               boards[move][CASTLING][0] != NoRights   )
16463                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16464            if(boards[move][CASTLING][2] != NoRights &&
16465               boards[move][CASTLING][1] != NoRights   )
16466                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16467            if(boards[move][CASTLING][5] != NoRights &&
16468               boards[move][CASTLING][3] != NoRights   )
16469                 *p++ = boards[move][CASTLING][3] + AAA;
16470            if(boards[move][CASTLING][5] != NoRights &&
16471               boards[move][CASTLING][4] != NoRights   )
16472                 *p++ = boards[move][CASTLING][4] + AAA;
16473      } else {
16474
16475         /* [HGM] write true castling rights */
16476         if( nrCastlingRights == 6 ) {
16477             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16478                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16479             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16480                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16481             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16482                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16483             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16484                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16485         }
16486      }
16487      if (q == p) *p++ = '-'; /* No castling rights */
16488      *p++ = ' ';
16489   }
16490
16491   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16492      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16493     /* En passant target square */
16494     if (move > backwardMostMove) {
16495         fromX = moveList[move - 1][0] - AAA;
16496         fromY = moveList[move - 1][1] - ONE;
16497         toX = moveList[move - 1][2] - AAA;
16498         toY = moveList[move - 1][3] - ONE;
16499         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16500             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16501             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16502             fromX == toX) {
16503             /* 2-square pawn move just happened */
16504             *p++ = toX + AAA;
16505             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16506         } else {
16507             *p++ = '-';
16508         }
16509     } else if(move == backwardMostMove) {
16510         // [HGM] perhaps we should always do it like this, and forget the above?
16511         if((signed char)boards[move][EP_STATUS] >= 0) {
16512             *p++ = boards[move][EP_STATUS] + AAA;
16513             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16514         } else {
16515             *p++ = '-';
16516         }
16517     } else {
16518         *p++ = '-';
16519     }
16520     *p++ = ' ';
16521   }
16522   }
16523
16524     /* [HGM] find reversible plies */
16525     {   int i = 0, j=move;
16526
16527         if (appData.debugMode) { int k;
16528             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16529             for(k=backwardMostMove; k<=forwardMostMove; k++)
16530                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16531
16532         }
16533
16534         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16535         if( j == backwardMostMove ) i += initialRulePlies;
16536         sprintf(p, "%d ", i);
16537         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16538     }
16539     /* Fullmove number */
16540     sprintf(p, "%d", (move / 2) + 1);
16541
16542     return StrSave(buf);
16543 }
16544
16545 Boolean
16546 ParseFEN(board, blackPlaysFirst, fen)
16547     Board board;
16548      int *blackPlaysFirst;
16549      char *fen;
16550 {
16551     int i, j;
16552     char *p, c;
16553     int emptycount;
16554     ChessSquare piece;
16555
16556     p = fen;
16557
16558     /* [HGM] by default clear Crazyhouse holdings, if present */
16559     if(gameInfo.holdingsWidth) {
16560        for(i=0; i<BOARD_HEIGHT; i++) {
16561            board[i][0]             = EmptySquare; /* black holdings */
16562            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16563            board[i][1]             = (ChessSquare) 0; /* black counts */
16564            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16565        }
16566     }
16567
16568     /* Piece placement data */
16569     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16570         j = 0;
16571         for (;;) {
16572             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16573                 if (*p == '/') p++;
16574                 emptycount = gameInfo.boardWidth - j;
16575                 while (emptycount--)
16576                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16577                 break;
16578 #if(BOARD_FILES >= 10)
16579             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16580                 p++; emptycount=10;
16581                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16582                 while (emptycount--)
16583                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16584 #endif
16585             } else if (isdigit(*p)) {
16586                 emptycount = *p++ - '0';
16587                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16588                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16589                 while (emptycount--)
16590                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16591             } else if (*p == '+' || isalpha(*p)) {
16592                 if (j >= gameInfo.boardWidth) return FALSE;
16593                 if(*p=='+') {
16594                     piece = CharToPiece(*++p);
16595                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16596                     piece = (ChessSquare) (PROMOTED piece ); p++;
16597                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16598                 } else piece = CharToPiece(*p++);
16599
16600                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16601                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16602                     piece = (ChessSquare) (PROMOTED piece);
16603                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16604                     p++;
16605                 }
16606                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16607             } else {
16608                 return FALSE;
16609             }
16610         }
16611     }
16612     while (*p == '/' || *p == ' ') p++;
16613
16614     /* [HGM] look for Crazyhouse holdings here */
16615     while(*p==' ') p++;
16616     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16617         if(*p == '[') p++;
16618         if(*p == '-' ) p++; /* empty holdings */ else {
16619             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16620             /* if we would allow FEN reading to set board size, we would   */
16621             /* have to add holdings and shift the board read so far here   */
16622             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16623                 p++;
16624                 if((int) piece >= (int) BlackPawn ) {
16625                     i = (int)piece - (int)BlackPawn;
16626                     i = PieceToNumber((ChessSquare)i);
16627                     if( i >= gameInfo.holdingsSize ) return FALSE;
16628                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16629                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16630                 } else {
16631                     i = (int)piece - (int)WhitePawn;
16632                     i = PieceToNumber((ChessSquare)i);
16633                     if( i >= gameInfo.holdingsSize ) return FALSE;
16634                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16635                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16636                 }
16637             }
16638         }
16639         if(*p == ']') p++;
16640     }
16641
16642     while(*p == ' ') p++;
16643
16644     /* Active color */
16645     c = *p++;
16646     if(appData.colorNickNames) {
16647       if( c == appData.colorNickNames[0] ) c = 'w'; else
16648       if( c == appData.colorNickNames[1] ) c = 'b';
16649     }
16650     switch (c) {
16651       case 'w':
16652         *blackPlaysFirst = FALSE;
16653         break;
16654       case 'b':
16655         *blackPlaysFirst = TRUE;
16656         break;
16657       default:
16658         return FALSE;
16659     }
16660
16661     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16662     /* return the extra info in global variiables             */
16663
16664     /* set defaults in case FEN is incomplete */
16665     board[EP_STATUS] = EP_UNKNOWN;
16666     for(i=0; i<nrCastlingRights; i++ ) {
16667         board[CASTLING][i] =
16668             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16669     }   /* assume possible unless obviously impossible */
16670     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16671     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16672     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16673                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16674     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16675     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16676     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16677                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16678     FENrulePlies = 0;
16679
16680     while(*p==' ') p++;
16681     if(nrCastlingRights) {
16682       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16683           /* castling indicator present, so default becomes no castlings */
16684           for(i=0; i<nrCastlingRights; i++ ) {
16685                  board[CASTLING][i] = NoRights;
16686           }
16687       }
16688       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16689              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16690              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16691              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16692         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16693
16694         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16695             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16696             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16697         }
16698         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16699             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16700         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16701                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16702         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16703                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16704         switch(c) {
16705           case'K':
16706               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16707               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16708               board[CASTLING][2] = whiteKingFile;
16709               break;
16710           case'Q':
16711               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16712               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16713               board[CASTLING][2] = whiteKingFile;
16714               break;
16715           case'k':
16716               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16717               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16718               board[CASTLING][5] = blackKingFile;
16719               break;
16720           case'q':
16721               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16722               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16723               board[CASTLING][5] = blackKingFile;
16724           case '-':
16725               break;
16726           default: /* FRC castlings */
16727               if(c >= 'a') { /* black rights */
16728                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16729                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16730                   if(i == BOARD_RGHT) break;
16731                   board[CASTLING][5] = i;
16732                   c -= AAA;
16733                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16734                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16735                   if(c > i)
16736                       board[CASTLING][3] = c;
16737                   else
16738                       board[CASTLING][4] = c;
16739               } else { /* white rights */
16740                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16741                     if(board[0][i] == WhiteKing) break;
16742                   if(i == BOARD_RGHT) break;
16743                   board[CASTLING][2] = i;
16744                   c -= AAA - 'a' + 'A';
16745                   if(board[0][c] >= WhiteKing) break;
16746                   if(c > i)
16747                       board[CASTLING][0] = c;
16748                   else
16749                       board[CASTLING][1] = c;
16750               }
16751         }
16752       }
16753       for(i=0; i<nrCastlingRights; i++)
16754         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16755     if (appData.debugMode) {
16756         fprintf(debugFP, "FEN castling rights:");
16757         for(i=0; i<nrCastlingRights; i++)
16758         fprintf(debugFP, " %d", board[CASTLING][i]);
16759         fprintf(debugFP, "\n");
16760     }
16761
16762       while(*p==' ') p++;
16763     }
16764
16765     /* read e.p. field in games that know e.p. capture */
16766     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16767        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16768       if(*p=='-') {
16769         p++; board[EP_STATUS] = EP_NONE;
16770       } else {
16771          char c = *p++ - AAA;
16772
16773          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16774          if(*p >= '0' && *p <='9') p++;
16775          board[EP_STATUS] = c;
16776       }
16777     }
16778
16779
16780     if(sscanf(p, "%d", &i) == 1) {
16781         FENrulePlies = i; /* 50-move ply counter */
16782         /* (The move number is still ignored)    */
16783     }
16784
16785     return TRUE;
16786 }
16787
16788 void
16789 EditPositionPasteFEN(char *fen)
16790 {
16791   if (fen != NULL) {
16792     Board initial_position;
16793
16794     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16795       DisplayError(_("Bad FEN position in clipboard"), 0);
16796       return ;
16797     } else {
16798       int savedBlackPlaysFirst = blackPlaysFirst;
16799       EditPositionEvent();
16800       blackPlaysFirst = savedBlackPlaysFirst;
16801       CopyBoard(boards[0], initial_position);
16802       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16803       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16804       DisplayBothClocks();
16805       DrawPosition(FALSE, boards[currentMove]);
16806     }
16807   }
16808 }
16809
16810 static char cseq[12] = "\\   ";
16811
16812 Boolean set_cont_sequence(char *new_seq)
16813 {
16814     int len;
16815     Boolean ret;
16816
16817     // handle bad attempts to set the sequence
16818         if (!new_seq)
16819                 return 0; // acceptable error - no debug
16820
16821     len = strlen(new_seq);
16822     ret = (len > 0) && (len < sizeof(cseq));
16823     if (ret)
16824       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16825     else if (appData.debugMode)
16826       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16827     return ret;
16828 }
16829
16830 /*
16831     reformat a source message so words don't cross the width boundary.  internal
16832     newlines are not removed.  returns the wrapped size (no null character unless
16833     included in source message).  If dest is NULL, only calculate the size required
16834     for the dest buffer.  lp argument indicats line position upon entry, and it's
16835     passed back upon exit.
16836 */
16837 int wrap(char *dest, char *src, int count, int width, int *lp)
16838 {
16839     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16840
16841     cseq_len = strlen(cseq);
16842     old_line = line = *lp;
16843     ansi = len = clen = 0;
16844
16845     for (i=0; i < count; i++)
16846     {
16847         if (src[i] == '\033')
16848             ansi = 1;
16849
16850         // if we hit the width, back up
16851         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16852         {
16853             // store i & len in case the word is too long
16854             old_i = i, old_len = len;
16855
16856             // find the end of the last word
16857             while (i && src[i] != ' ' && src[i] != '\n')
16858             {
16859                 i--;
16860                 len--;
16861             }
16862
16863             // word too long?  restore i & len before splitting it
16864             if ((old_i-i+clen) >= width)
16865             {
16866                 i = old_i;
16867                 len = old_len;
16868             }
16869
16870             // extra space?
16871             if (i && src[i-1] == ' ')
16872                 len--;
16873
16874             if (src[i] != ' ' && src[i] != '\n')
16875             {
16876                 i--;
16877                 if (len)
16878                     len--;
16879             }
16880
16881             // now append the newline and continuation sequence
16882             if (dest)
16883                 dest[len] = '\n';
16884             len++;
16885             if (dest)
16886                 strncpy(dest+len, cseq, cseq_len);
16887             len += cseq_len;
16888             line = cseq_len;
16889             clen = cseq_len;
16890             continue;
16891         }
16892
16893         if (dest)
16894             dest[len] = src[i];
16895         len++;
16896         if (!ansi)
16897             line++;
16898         if (src[i] == '\n')
16899             line = 0;
16900         if (src[i] == 'm')
16901             ansi = 0;
16902     }
16903     if (dest && appData.debugMode)
16904     {
16905         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16906             count, width, line, len, *lp);
16907         show_bytes(debugFP, src, count);
16908         fprintf(debugFP, "\ndest: ");
16909         show_bytes(debugFP, dest, len);
16910         fprintf(debugFP, "\n");
16911     }
16912     *lp = dest ? line : old_line;
16913
16914     return len;
16915 }
16916
16917 // [HGM] vari: routines for shelving variations
16918 Boolean modeRestore = FALSE;
16919
16920 void
16921 PushInner(int firstMove, int lastMove)
16922 {
16923         int i, j, nrMoves = lastMove - firstMove;
16924
16925         // push current tail of game on stack
16926         savedResult[storedGames] = gameInfo.result;
16927         savedDetails[storedGames] = gameInfo.resultDetails;
16928         gameInfo.resultDetails = NULL;
16929         savedFirst[storedGames] = firstMove;
16930         savedLast [storedGames] = lastMove;
16931         savedFramePtr[storedGames] = framePtr;
16932         framePtr -= nrMoves; // reserve space for the boards
16933         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16934             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16935             for(j=0; j<MOVE_LEN; j++)
16936                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16937             for(j=0; j<2*MOVE_LEN; j++)
16938                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16939             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16940             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16941             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16942             pvInfoList[firstMove+i-1].depth = 0;
16943             commentList[framePtr+i] = commentList[firstMove+i];
16944             commentList[firstMove+i] = NULL;
16945         }
16946
16947         storedGames++;
16948         forwardMostMove = firstMove; // truncate game so we can start variation
16949 }
16950
16951 void
16952 PushTail(int firstMove, int lastMove)
16953 {
16954         if(appData.icsActive) { // only in local mode
16955                 forwardMostMove = currentMove; // mimic old ICS behavior
16956                 return;
16957         }
16958         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16959
16960         PushInner(firstMove, lastMove);
16961         if(storedGames == 1) GreyRevert(FALSE);
16962         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16963 }
16964
16965 void
16966 PopInner(Boolean annotate)
16967 {
16968         int i, j, nrMoves;
16969         char buf[8000], moveBuf[20];
16970
16971         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16972         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16973         nrMoves = savedLast[storedGames] - currentMove;
16974         if(annotate) {
16975                 int cnt = 10;
16976                 if(!WhiteOnMove(currentMove))
16977                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16978                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16979                 for(i=currentMove; i<forwardMostMove; i++) {
16980                         if(WhiteOnMove(i))
16981                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16982                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16983                         strcat(buf, moveBuf);
16984                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16985                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16986                 }
16987                 strcat(buf, ")");
16988         }
16989         for(i=1; i<=nrMoves; i++) { // copy last variation back
16990             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16991             for(j=0; j<MOVE_LEN; j++)
16992                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16993             for(j=0; j<2*MOVE_LEN; j++)
16994                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16995             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16996             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16997             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16998             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16999             commentList[currentMove+i] = commentList[framePtr+i];
17000             commentList[framePtr+i] = NULL;
17001         }
17002         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17003         framePtr = savedFramePtr[storedGames];
17004         gameInfo.result = savedResult[storedGames];
17005         if(gameInfo.resultDetails != NULL) {
17006             free(gameInfo.resultDetails);
17007       }
17008         gameInfo.resultDetails = savedDetails[storedGames];
17009         forwardMostMove = currentMove + nrMoves;
17010 }
17011
17012 Boolean
17013 PopTail(Boolean annotate)
17014 {
17015         if(appData.icsActive) return FALSE; // only in local mode
17016         if(!storedGames) return FALSE; // sanity
17017         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17018
17019         PopInner(annotate);
17020         if(currentMove < forwardMostMove) ForwardEvent(); else
17021         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17022
17023         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17024         return TRUE;
17025 }
17026
17027 void
17028 CleanupTail()
17029 {       // remove all shelved variations
17030         int i;
17031         for(i=0; i<storedGames; i++) {
17032             if(savedDetails[i])
17033                 free(savedDetails[i]);
17034             savedDetails[i] = NULL;
17035         }
17036         for(i=framePtr; i<MAX_MOVES; i++) {
17037                 if(commentList[i]) free(commentList[i]);
17038                 commentList[i] = NULL;
17039         }
17040         framePtr = MAX_MOVES-1;
17041         storedGames = 0;
17042 }
17043
17044 void
17045 LoadVariation(int index, char *text)
17046 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17047         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17048         int level = 0, move;
17049
17050         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17051         // first find outermost bracketing variation
17052         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17053             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17054                 if(*p == '{') wait = '}'; else
17055                 if(*p == '[') wait = ']'; else
17056                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17057                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17058             }
17059             if(*p == wait) wait = NULLCHAR; // closing ]} found
17060             p++;
17061         }
17062         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17063         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17064         end[1] = NULLCHAR; // clip off comment beyond variation
17065         ToNrEvent(currentMove-1);
17066         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17067         // kludge: use ParsePV() to append variation to game
17068         move = currentMove;
17069         ParsePV(start, TRUE, TRUE);
17070         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17071         ClearPremoveHighlights();
17072         CommentPopDown();
17073         ToNrEvent(currentMove+1);
17074 }
17075