Add -afterTourney option
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy( char *dst, const char *src, size_t count )
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble(u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags(index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating(str)
647   char *str;
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine(ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions(ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737 "first",
738 "second"
739 };
740
741 void
742 InitEngine(ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine(ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     appData.seedBase = random() + (random()<<15);
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len >= MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len >= MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for draw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantGrand:      /* should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void EscapeExpand(char *p, char *q)
1718 {       // [HGM] initstring: routine to shape up string arguments
1719         while(*p++ = *q++) if(p[-1] == '\\')
1720             switch(*q++) {
1721                 case 'n': p[-1] = '\n'; break;
1722                 case 'r': p[-1] = '\r'; break;
1723                 case 't': p[-1] = '\t'; break;
1724                 case '\\': p[-1] = '\\'; break;
1725                 case 0: *p = 0; return;
1726                 default: p[-1] = q[-1]; break;
1727             }
1728 }
1729
1730 void
1731 show_bytes(fp, buf, count)
1732      FILE *fp;
1733      char *buf;
1734      int count;
1735 {
1736     while (count--) {
1737         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1738             fprintf(fp, "\\%03o", *buf & 0xff);
1739         } else {
1740             putc(*buf, fp);
1741         }
1742         buf++;
1743     }
1744     fflush(fp);
1745 }
1746
1747 /* Returns an errno value */
1748 int
1749 OutputMaybeTelnet(pr, message, count, outError)
1750      ProcRef pr;
1751      char *message;
1752      int count;
1753      int *outError;
1754 {
1755     char buf[8192], *p, *q, *buflim;
1756     int left, newcount, outcount;
1757
1758     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1759         *appData.gateway != NULLCHAR) {
1760         if (appData.debugMode) {
1761             fprintf(debugFP, ">ICS: ");
1762             show_bytes(debugFP, message, count);
1763             fprintf(debugFP, "\n");
1764         }
1765         return OutputToProcess(pr, message, count, outError);
1766     }
1767
1768     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769     p = message;
1770     q = buf;
1771     left = count;
1772     newcount = 0;
1773     while (left) {
1774         if (q >= buflim) {
1775             if (appData.debugMode) {
1776                 fprintf(debugFP, ">ICS: ");
1777                 show_bytes(debugFP, buf, newcount);
1778                 fprintf(debugFP, "\n");
1779             }
1780             outcount = OutputToProcess(pr, buf, newcount, outError);
1781             if (outcount < newcount) return -1; /* to be sure */
1782             q = buf;
1783             newcount = 0;
1784         }
1785         if (*p == '\n') {
1786             *q++ = '\r';
1787             newcount++;
1788         } else if (((unsigned char) *p) == TN_IAC) {
1789             *q++ = (char) TN_IAC;
1790             newcount ++;
1791         }
1792         *q++ = *p++;
1793         newcount++;
1794         left--;
1795     }
1796     if (appData.debugMode) {
1797         fprintf(debugFP, ">ICS: ");
1798         show_bytes(debugFP, buf, newcount);
1799         fprintf(debugFP, "\n");
1800     }
1801     outcount = OutputToProcess(pr, buf, newcount, outError);
1802     if (outcount < newcount) return -1; /* to be sure */
1803     return count;
1804 }
1805
1806 void
1807 read_from_player(isr, closure, message, count, error)
1808      InputSourceRef isr;
1809      VOIDSTAR closure;
1810      char *message;
1811      int count;
1812      int error;
1813 {
1814     int outError, outCount;
1815     static int gotEof = 0;
1816
1817     /* Pass data read from player on to ICS */
1818     if (count > 0) {
1819         gotEof = 0;
1820         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1821         if (outCount < count) {
1822             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1823         }
1824     } else if (count < 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1827     } else if (gotEof++ > 0) {
1828         RemoveInputSource(isr);
1829         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1830     }
1831 }
1832
1833 void
1834 KeepAlive()
1835 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1836     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1837     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1838     SendToICS("date\n");
1839     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1840 }
1841
1842 /* added routine for printf style output to ics */
1843 void ics_printf(char *format, ...)
1844 {
1845     char buffer[MSG_SIZ];
1846     va_list args;
1847
1848     va_start(args, format);
1849     vsnprintf(buffer, sizeof(buffer), format, args);
1850     buffer[sizeof(buffer)-1] = '\0';
1851     SendToICS(buffer);
1852     va_end(args);
1853 }
1854
1855 void
1856 SendToICS(s)
1857      char *s;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NoProc) return;
1862
1863     count = strlen(s);
1864     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1865     if (outCount < count) {
1866         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867     }
1868 }
1869
1870 /* This is used for sending logon scripts to the ICS. Sending
1871    without a delay causes problems when using timestamp on ICC
1872    (at least on my machine). */
1873 void
1874 SendToICSDelayed(s,msdelay)
1875      char *s;
1876      long msdelay;
1877 {
1878     int count, outCount, outError;
1879
1880     if (icsPR == NoProc) return;
1881
1882     count = strlen(s);
1883     if (appData.debugMode) {
1884         fprintf(debugFP, ">ICS: ");
1885         show_bytes(debugFP, s, count);
1886         fprintf(debugFP, "\n");
1887     }
1888     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1889                                       msdelay);
1890     if (outCount < count) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895
1896 /* Remove all highlighting escape sequences in s
1897    Also deletes any suffix starting with '('
1898    */
1899 char *
1900 StripHighlightAndTitle(s)
1901      char *s;
1902 {
1903     static char retbuf[MSG_SIZ];
1904     char *p = retbuf;
1905
1906     while (*s != NULLCHAR) {
1907         while (*s == '\033') {
1908             while (*s != NULLCHAR && !isalpha(*s)) s++;
1909             if (*s != NULLCHAR) s++;
1910         }
1911         while (*s != NULLCHAR && *s != '\033') {
1912             if (*s == '(' || *s == '[') {
1913                 *p = NULLCHAR;
1914                 return retbuf;
1915             }
1916             *p++ = *s++;
1917         }
1918     }
1919     *p = NULLCHAR;
1920     return retbuf;
1921 }
1922
1923 /* Remove all highlighting escape sequences in s */
1924 char *
1925 StripHighlight(s)
1926      char *s;
1927 {
1928     static char retbuf[MSG_SIZ];
1929     char *p = retbuf;
1930
1931     while (*s != NULLCHAR) {
1932         while (*s == '\033') {
1933             while (*s != NULLCHAR && !isalpha(*s)) s++;
1934             if (*s != NULLCHAR) s++;
1935         }
1936         while (*s != NULLCHAR && *s != '\033') {
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 char *variantNames[] = VARIANT_NAMES;
1945 char *
1946 VariantName(v)
1947      VariantClass v;
1948 {
1949     return variantNames[v];
1950 }
1951
1952
1953 /* Identify a variant from the strings the chess servers use or the
1954    PGN Variant tag names we use. */
1955 VariantClass
1956 StringToVariant(e)
1957      char *e;
1958 {
1959     char *p;
1960     int wnum = -1;
1961     VariantClass v = VariantNormal;
1962     int i, found = FALSE;
1963     char buf[MSG_SIZ];
1964     int len;
1965
1966     if (!e) return v;
1967
1968     /* [HGM] skip over optional board-size prefixes */
1969     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1970         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1971         while( *e++ != '_');
1972     }
1973
1974     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1975         v = VariantNormal;
1976         found = TRUE;
1977     } else
1978     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1979       if (StrCaseStr(e, variantNames[i])) {
1980         v = (VariantClass) i;
1981         found = TRUE;
1982         break;
1983       }
1984     }
1985
1986     if (!found) {
1987       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1988           || StrCaseStr(e, "wild/fr")
1989           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1990         v = VariantFischeRandom;
1991       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1992                  (i = 1, p = StrCaseStr(e, "w"))) {
1993         p += i;
1994         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1995         if (isdigit(*p)) {
1996           wnum = atoi(p);
1997         } else {
1998           wnum = -1;
1999         }
2000         switch (wnum) {
2001         case 0: /* FICS only, actually */
2002         case 1:
2003           /* Castling legal even if K starts on d-file */
2004           v = VariantWildCastle;
2005           break;
2006         case 2:
2007         case 3:
2008         case 4:
2009           /* Castling illegal even if K & R happen to start in
2010              normal positions. */
2011           v = VariantNoCastle;
2012           break;
2013         case 5:
2014         case 7:
2015         case 8:
2016         case 10:
2017         case 11:
2018         case 12:
2019         case 13:
2020         case 14:
2021         case 15:
2022         case 18:
2023         case 19:
2024           /* Castling legal iff K & R start in normal positions */
2025           v = VariantNormal;
2026           break;
2027         case 6:
2028         case 20:
2029         case 21:
2030           /* Special wilds for position setup; unclear what to do here */
2031           v = VariantLoadable;
2032           break;
2033         case 9:
2034           /* Bizarre ICC game */
2035           v = VariantTwoKings;
2036           break;
2037         case 16:
2038           v = VariantKriegspiel;
2039           break;
2040         case 17:
2041           v = VariantLosers;
2042           break;
2043         case 22:
2044           v = VariantFischeRandom;
2045           break;
2046         case 23:
2047           v = VariantCrazyhouse;
2048           break;
2049         case 24:
2050           v = VariantBughouse;
2051           break;
2052         case 25:
2053           v = Variant3Check;
2054           break;
2055         case 26:
2056           /* Not quite the same as FICS suicide! */
2057           v = VariantGiveaway;
2058           break;
2059         case 27:
2060           v = VariantAtomic;
2061           break;
2062         case 28:
2063           v = VariantShatranj;
2064           break;
2065
2066         /* Temporary names for future ICC types.  The name *will* change in
2067            the next xboard/WinBoard release after ICC defines it. */
2068         case 29:
2069           v = Variant29;
2070           break;
2071         case 30:
2072           v = Variant30;
2073           break;
2074         case 31:
2075           v = Variant31;
2076           break;
2077         case 32:
2078           v = Variant32;
2079           break;
2080         case 33:
2081           v = Variant33;
2082           break;
2083         case 34:
2084           v = Variant34;
2085           break;
2086         case 35:
2087           v = Variant35;
2088           break;
2089         case 36:
2090           v = Variant36;
2091           break;
2092         case 37:
2093           v = VariantShogi;
2094           break;
2095         case 38:
2096           v = VariantXiangqi;
2097           break;
2098         case 39:
2099           v = VariantCourier;
2100           break;
2101         case 40:
2102           v = VariantGothic;
2103           break;
2104         case 41:
2105           v = VariantCapablanca;
2106           break;
2107         case 42:
2108           v = VariantKnightmate;
2109           break;
2110         case 43:
2111           v = VariantFairy;
2112           break;
2113         case 44:
2114           v = VariantCylinder;
2115           break;
2116         case 45:
2117           v = VariantFalcon;
2118           break;
2119         case 46:
2120           v = VariantCapaRandom;
2121           break;
2122         case 47:
2123           v = VariantBerolina;
2124           break;
2125         case 48:
2126           v = VariantJanus;
2127           break;
2128         case 49:
2129           v = VariantSuper;
2130           break;
2131         case 50:
2132           v = VariantGreat;
2133           break;
2134         case -1:
2135           /* Found "wild" or "w" in the string but no number;
2136              must assume it's normal chess. */
2137           v = VariantNormal;
2138           break;
2139         default:
2140           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2141           if( (len >= MSG_SIZ) && appData.debugMode )
2142             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2143
2144           DisplayError(buf, 0);
2145           v = VariantUnknown;
2146           break;
2147         }
2148       }
2149     }
2150     if (appData.debugMode) {
2151       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2152               e, wnum, VariantName(v));
2153     }
2154     return v;
2155 }
2156
2157 static int leftover_start = 0, leftover_len = 0;
2158 char star_match[STAR_MATCH_N][MSG_SIZ];
2159
2160 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2161    advance *index beyond it, and set leftover_start to the new value of
2162    *index; else return FALSE.  If pattern contains the character '*', it
2163    matches any sequence of characters not containing '\r', '\n', or the
2164    character following the '*' (if any), and the matched sequence(s) are
2165    copied into star_match.
2166    */
2167 int
2168 looking_at(buf, index, pattern)
2169      char *buf;
2170      int *index;
2171      char *pattern;
2172 {
2173     char *bufp = &buf[*index], *patternp = pattern;
2174     int star_count = 0;
2175     char *matchp = star_match[0];
2176
2177     for (;;) {
2178         if (*patternp == NULLCHAR) {
2179             *index = leftover_start = bufp - buf;
2180             *matchp = NULLCHAR;
2181             return TRUE;
2182         }
2183         if (*bufp == NULLCHAR) return FALSE;
2184         if (*patternp == '*') {
2185             if (*bufp == *(patternp + 1)) {
2186                 *matchp = NULLCHAR;
2187                 matchp = star_match[++star_count];
2188                 patternp += 2;
2189                 bufp++;
2190                 continue;
2191             } else if (*bufp == '\n' || *bufp == '\r') {
2192                 patternp++;
2193                 if (*patternp == NULLCHAR)
2194                   continue;
2195                 else
2196                   return FALSE;
2197             } else {
2198                 *matchp++ = *bufp++;
2199                 continue;
2200             }
2201         }
2202         if (*patternp != *bufp) return FALSE;
2203         patternp++;
2204         bufp++;
2205     }
2206 }
2207
2208 void
2209 SendToPlayer(data, length)
2210      char *data;
2211      int length;
2212 {
2213     int error, outCount;
2214     outCount = OutputToProcess(NoProc, data, length, &error);
2215     if (outCount < length) {
2216         DisplayFatalError(_("Error writing to display"), error, 1);
2217     }
2218 }
2219
2220 void
2221 PackHolding(packed, holding)
2222      char packed[];
2223      char *holding;
2224 {
2225     char *p = holding;
2226     char *q = packed;
2227     int runlength = 0;
2228     int curr = 9999;
2229     do {
2230         if (*p == curr) {
2231             runlength++;
2232         } else {
2233             switch (runlength) {
2234               case 0:
2235                 break;
2236               case 1:
2237                 *q++ = curr;
2238                 break;
2239               case 2:
2240                 *q++ = curr;
2241                 *q++ = curr;
2242                 break;
2243               default:
2244                 sprintf(q, "%d", runlength);
2245                 while (*q) q++;
2246                 *q++ = curr;
2247                 break;
2248             }
2249             runlength = 1;
2250             curr = *p;
2251         }
2252     } while (*p++);
2253     *q = NULLCHAR;
2254 }
2255
2256 /* Telnet protocol requests from the front end */
2257 void
2258 TelnetRequest(ddww, option)
2259      unsigned char ddww, option;
2260 {
2261     unsigned char msg[3];
2262     int outCount, outError;
2263
2264     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2265
2266     if (appData.debugMode) {
2267         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2268         switch (ddww) {
2269           case TN_DO:
2270             ddwwStr = "DO";
2271             break;
2272           case TN_DONT:
2273             ddwwStr = "DONT";
2274             break;
2275           case TN_WILL:
2276             ddwwStr = "WILL";
2277             break;
2278           case TN_WONT:
2279             ddwwStr = "WONT";
2280             break;
2281           default:
2282             ddwwStr = buf1;
2283             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2284             break;
2285         }
2286         switch (option) {
2287           case TN_ECHO:
2288             optionStr = "ECHO";
2289             break;
2290           default:
2291             optionStr = buf2;
2292             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2293             break;
2294         }
2295         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2296     }
2297     msg[0] = TN_IAC;
2298     msg[1] = ddww;
2299     msg[2] = option;
2300     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2301     if (outCount < 3) {
2302         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2303     }
2304 }
2305
2306 void
2307 DoEcho()
2308 {
2309     if (!appData.icsActive) return;
2310     TelnetRequest(TN_DO, TN_ECHO);
2311 }
2312
2313 void
2314 DontEcho()
2315 {
2316     if (!appData.icsActive) return;
2317     TelnetRequest(TN_DONT, TN_ECHO);
2318 }
2319
2320 void
2321 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2322 {
2323     /* put the holdings sent to us by the server on the board holdings area */
2324     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2325     char p;
2326     ChessSquare piece;
2327
2328     if(gameInfo.holdingsWidth < 2)  return;
2329     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2330         return; // prevent overwriting by pre-board holdings
2331
2332     if( (int)lowestPiece >= BlackPawn ) {
2333         holdingsColumn = 0;
2334         countsColumn = 1;
2335         holdingsStartRow = BOARD_HEIGHT-1;
2336         direction = -1;
2337     } else {
2338         holdingsColumn = BOARD_WIDTH-1;
2339         countsColumn = BOARD_WIDTH-2;
2340         holdingsStartRow = 0;
2341         direction = 1;
2342     }
2343
2344     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2345         board[i][holdingsColumn] = EmptySquare;
2346         board[i][countsColumn]   = (ChessSquare) 0;
2347     }
2348     while( (p=*holdings++) != NULLCHAR ) {
2349         piece = CharToPiece( ToUpper(p) );
2350         if(piece == EmptySquare) continue;
2351         /*j = (int) piece - (int) WhitePawn;*/
2352         j = PieceToNumber(piece);
2353         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2354         if(j < 0) continue;               /* should not happen */
2355         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2356         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2357         board[holdingsStartRow+j*direction][countsColumn]++;
2358     }
2359 }
2360
2361
2362 void
2363 VariantSwitch(Board board, VariantClass newVariant)
2364 {
2365    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2366    static Board oldBoard;
2367
2368    startedFromPositionFile = FALSE;
2369    if(gameInfo.variant == newVariant) return;
2370
2371    /* [HGM] This routine is called each time an assignment is made to
2372     * gameInfo.variant during a game, to make sure the board sizes
2373     * are set to match the new variant. If that means adding or deleting
2374     * holdings, we shift the playing board accordingly
2375     * This kludge is needed because in ICS observe mode, we get boards
2376     * of an ongoing game without knowing the variant, and learn about the
2377     * latter only later. This can be because of the move list we requested,
2378     * in which case the game history is refilled from the beginning anyway,
2379     * but also when receiving holdings of a crazyhouse game. In the latter
2380     * case we want to add those holdings to the already received position.
2381     */
2382
2383
2384    if (appData.debugMode) {
2385      fprintf(debugFP, "Switch board from %s to %s\n",
2386              VariantName(gameInfo.variant), VariantName(newVariant));
2387      setbuf(debugFP, NULL);
2388    }
2389    shuffleOpenings = 0;       /* [HGM] shuffle */
2390    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2391    switch(newVariant)
2392      {
2393      case VariantShogi:
2394        newWidth = 9;  newHeight = 9;
2395        gameInfo.holdingsSize = 7;
2396      case VariantBughouse:
2397      case VariantCrazyhouse:
2398        newHoldingsWidth = 2; break;
2399      case VariantGreat:
2400        newWidth = 10;
2401      case VariantSuper:
2402        newHoldingsWidth = 2;
2403        gameInfo.holdingsSize = 8;
2404        break;
2405      case VariantGothic:
2406      case VariantCapablanca:
2407      case VariantCapaRandom:
2408        newWidth = 10;
2409      default:
2410        newHoldingsWidth = gameInfo.holdingsSize = 0;
2411      };
2412
2413    if(newWidth  != gameInfo.boardWidth  ||
2414       newHeight != gameInfo.boardHeight ||
2415       newHoldingsWidth != gameInfo.holdingsWidth ) {
2416
2417      /* shift position to new playing area, if needed */
2418      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2419        for(i=0; i<BOARD_HEIGHT; i++)
2420          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2421            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2422              board[i][j];
2423        for(i=0; i<newHeight; i++) {
2424          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2425          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2426        }
2427      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432      }
2433      gameInfo.boardWidth  = newWidth;
2434      gameInfo.boardHeight = newHeight;
2435      gameInfo.holdingsWidth = newHoldingsWidth;
2436      gameInfo.variant = newVariant;
2437      InitDrawingSizes(-2, 0);
2438    } else gameInfo.variant = newVariant;
2439    CopyBoard(oldBoard, board);   // remember correctly formatted board
2440      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2441    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2442 }
2443
2444 static int loggedOn = FALSE;
2445
2446 /*-- Game start info cache: --*/
2447 int gs_gamenum;
2448 char gs_kind[MSG_SIZ];
2449 static char player1Name[128] = "";
2450 static char player2Name[128] = "";
2451 static char cont_seq[] = "\n\\   ";
2452 static int player1Rating = -1;
2453 static int player2Rating = -1;
2454 /*----------------------------*/
2455
2456 ColorClass curColor = ColorNormal;
2457 int suppressKibitz = 0;
2458
2459 // [HGM] seekgraph
2460 Boolean soughtPending = FALSE;
2461 Boolean seekGraphUp;
2462 #define MAX_SEEK_ADS 200
2463 #define SQUARE 0x80
2464 char *seekAdList[MAX_SEEK_ADS];
2465 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2466 float tcList[MAX_SEEK_ADS];
2467 char colorList[MAX_SEEK_ADS];
2468 int nrOfSeekAds = 0;
2469 int minRating = 1010, maxRating = 2800;
2470 int hMargin = 10, vMargin = 20, h, w;
2471 extern int squareSize, lineGap;
2472
2473 void
2474 PlotSeekAd(int i)
2475 {
2476         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2477         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2478         if(r < minRating+100 && r >=0 ) r = minRating+100;
2479         if(r > maxRating) r = maxRating;
2480         if(tc < 1.) tc = 1.;
2481         if(tc > 95.) tc = 95.;
2482         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2483         y = ((double)r - minRating)/(maxRating - minRating)
2484             * (h-vMargin-squareSize/8-1) + vMargin;
2485         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2486         if(strstr(seekAdList[i], " u ")) color = 1;
2487         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2488            !strstr(seekAdList[i], "bullet") &&
2489            !strstr(seekAdList[i], "blitz") &&
2490            !strstr(seekAdList[i], "standard") ) color = 2;
2491         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2492         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2493 }
2494
2495 void
2496 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2497 {
2498         char buf[MSG_SIZ], *ext = "";
2499         VariantClass v = StringToVariant(type);
2500         if(strstr(type, "wild")) {
2501             ext = type + 4; // append wild number
2502             if(v == VariantFischeRandom) type = "chess960"; else
2503             if(v == VariantLoadable) type = "setup"; else
2504             type = VariantName(v);
2505         }
2506         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2507         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2508             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2509             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2510             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2511             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2512             seekNrList[nrOfSeekAds] = nr;
2513             zList[nrOfSeekAds] = 0;
2514             seekAdList[nrOfSeekAds++] = StrSave(buf);
2515             if(plot) PlotSeekAd(nrOfSeekAds-1);
2516         }
2517 }
2518
2519 void
2520 EraseSeekDot(int i)
2521 {
2522     int x = xList[i], y = yList[i], d=squareSize/4, k;
2523     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2524     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2525     // now replot every dot that overlapped
2526     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2527         int xx = xList[k], yy = yList[k];
2528         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2529             DrawSeekDot(xx, yy, colorList[k]);
2530     }
2531 }
2532
2533 void
2534 RemoveSeekAd(int nr)
2535 {
2536         int i;
2537         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2538             EraseSeekDot(i);
2539             if(seekAdList[i]) free(seekAdList[i]);
2540             seekAdList[i] = seekAdList[--nrOfSeekAds];
2541             seekNrList[i] = seekNrList[nrOfSeekAds];
2542             ratingList[i] = ratingList[nrOfSeekAds];
2543             colorList[i]  = colorList[nrOfSeekAds];
2544             tcList[i] = tcList[nrOfSeekAds];
2545             xList[i]  = xList[nrOfSeekAds];
2546             yList[i]  = yList[nrOfSeekAds];
2547             zList[i]  = zList[nrOfSeekAds];
2548             seekAdList[nrOfSeekAds] = NULL;
2549             break;
2550         }
2551 }
2552
2553 Boolean
2554 MatchSoughtLine(char *line)
2555 {
2556     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2557     int nr, base, inc, u=0; char dummy;
2558
2559     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2561        (u=1) &&
2562        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2563         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2564         // match: compact and save the line
2565         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2566         return TRUE;
2567     }
2568     return FALSE;
2569 }
2570
2571 int
2572 DrawSeekGraph()
2573 {
2574     int i;
2575     if(!seekGraphUp) return FALSE;
2576     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2577     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2578
2579     DrawSeekBackground(0, 0, w, h);
2580     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2581     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2582     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2583         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2584         yy = h-1-yy;
2585         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2586         if(i%500 == 0) {
2587             char buf[MSG_SIZ];
2588             snprintf(buf, MSG_SIZ, "%d", i);
2589             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2590         }
2591     }
2592     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2593     for(i=1; i<100; i+=(i<10?1:5)) {
2594         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2595         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2596         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2600         }
2601     }
2602     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2603     return TRUE;
2604 }
2605
2606 int SeekGraphClick(ClickType click, int x, int y, int moving)
2607 {
2608     static int lastDown = 0, displayed = 0, lastSecond;
2609     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2610         if(click == Release || moving) return FALSE;
2611         nrOfSeekAds = 0;
2612         soughtPending = TRUE;
2613         SendToICS(ics_prefix);
2614         SendToICS("sought\n"); // should this be "sought all"?
2615     } else { // issue challenge based on clicked ad
2616         int dist = 10000; int i, closest = 0, second = 0;
2617         for(i=0; i<nrOfSeekAds; i++) {
2618             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2619             if(d < dist) { dist = d; closest = i; }
2620             second += (d - zList[i] < 120); // count in-range ads
2621             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2622         }
2623         if(dist < 120) {
2624             char buf[MSG_SIZ];
2625             second = (second > 1);
2626             if(displayed != closest || second != lastSecond) {
2627                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2628                 lastSecond = second; displayed = closest;
2629             }
2630             if(click == Press) {
2631                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2632                 lastDown = closest;
2633                 return TRUE;
2634             } // on press 'hit', only show info
2635             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2636             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2637             SendToICS(ics_prefix);
2638             SendToICS(buf);
2639             return TRUE; // let incoming board of started game pop down the graph
2640         } else if(click == Release) { // release 'miss' is ignored
2641             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2642             if(moving == 2) { // right up-click
2643                 nrOfSeekAds = 0; // refresh graph
2644                 soughtPending = TRUE;
2645                 SendToICS(ics_prefix);
2646                 SendToICS("sought\n"); // should this be "sought all"?
2647             }
2648             return TRUE;
2649         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2650         // press miss or release hit 'pop down' seek graph
2651         seekGraphUp = FALSE;
2652         DrawPosition(TRUE, NULL);
2653     }
2654     return TRUE;
2655 }
2656
2657 void
2658 read_from_ics(isr, closure, data, count, error)
2659      InputSourceRef isr;
2660      VOIDSTAR closure;
2661      char *data;
2662      int count;
2663      int error;
2664 {
2665 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2666 #define STARTED_NONE 0
2667 #define STARTED_MOVES 1
2668 #define STARTED_BOARD 2
2669 #define STARTED_OBSERVE 3
2670 #define STARTED_HOLDINGS 4
2671 #define STARTED_CHATTER 5
2672 #define STARTED_COMMENT 6
2673 #define STARTED_MOVES_NOHIDE 7
2674
2675     static int started = STARTED_NONE;
2676     static char parse[20000];
2677     static int parse_pos = 0;
2678     static char buf[BUF_SIZE + 1];
2679     static int firstTime = TRUE, intfSet = FALSE;
2680     static ColorClass prevColor = ColorNormal;
2681     static int savingComment = FALSE;
2682     static int cmatch = 0; // continuation sequence match
2683     char *bp;
2684     char str[MSG_SIZ];
2685     int i, oldi;
2686     int buf_len;
2687     int next_out;
2688     int tkind;
2689     int backup;    /* [DM] For zippy color lines */
2690     char *p;
2691     char talker[MSG_SIZ]; // [HGM] chat
2692     int channel;
2693
2694     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2695
2696     if (appData.debugMode) {
2697       if (!error) {
2698         fprintf(debugFP, "<ICS: ");
2699         show_bytes(debugFP, data, count);
2700         fprintf(debugFP, "\n");
2701       }
2702     }
2703
2704     if (appData.debugMode) { int f = forwardMostMove;
2705         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2706                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2707                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2708     }
2709     if (count > 0) {
2710         /* If last read ended with a partial line that we couldn't parse,
2711            prepend it to the new read and try again. */
2712         if (leftover_len > 0) {
2713             for (i=0; i<leftover_len; i++)
2714               buf[i] = buf[leftover_start + i];
2715         }
2716
2717     /* copy new characters into the buffer */
2718     bp = buf + leftover_len;
2719     buf_len=leftover_len;
2720     for (i=0; i<count; i++)
2721     {
2722         // ignore these
2723         if (data[i] == '\r')
2724             continue;
2725
2726         // join lines split by ICS?
2727         if (!appData.noJoin)
2728         {
2729             /*
2730                 Joining just consists of finding matches against the
2731                 continuation sequence, and discarding that sequence
2732                 if found instead of copying it.  So, until a match
2733                 fails, there's nothing to do since it might be the
2734                 complete sequence, and thus, something we don't want
2735                 copied.
2736             */
2737             if (data[i] == cont_seq[cmatch])
2738             {
2739                 cmatch++;
2740                 if (cmatch == strlen(cont_seq))
2741                 {
2742                     cmatch = 0; // complete match.  just reset the counter
2743
2744                     /*
2745                         it's possible for the ICS to not include the space
2746                         at the end of the last word, making our [correct]
2747                         join operation fuse two separate words.  the server
2748                         does this when the space occurs at the width setting.
2749                     */
2750                     if (!buf_len || buf[buf_len-1] != ' ')
2751                     {
2752                         *bp++ = ' ';
2753                         buf_len++;
2754                     }
2755                 }
2756                 continue;
2757             }
2758             else if (cmatch)
2759             {
2760                 /*
2761                     match failed, so we have to copy what matched before
2762                     falling through and copying this character.  In reality,
2763                     this will only ever be just the newline character, but
2764                     it doesn't hurt to be precise.
2765                 */
2766                 strncpy(bp, cont_seq, cmatch);
2767                 bp += cmatch;
2768                 buf_len += cmatch;
2769                 cmatch = 0;
2770             }
2771         }
2772
2773         // copy this char
2774         *bp++ = data[i];
2775         buf_len++;
2776     }
2777
2778         buf[buf_len] = NULLCHAR;
2779 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2780         next_out = 0;
2781         leftover_start = 0;
2782
2783         i = 0;
2784         while (i < buf_len) {
2785             /* Deal with part of the TELNET option negotiation
2786                protocol.  We refuse to do anything beyond the
2787                defaults, except that we allow the WILL ECHO option,
2788                which ICS uses to turn off password echoing when we are
2789                directly connected to it.  We reject this option
2790                if localLineEditing mode is on (always on in xboard)
2791                and we are talking to port 23, which might be a real
2792                telnet server that will try to keep WILL ECHO on permanently.
2793              */
2794             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2795                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2796                 unsigned char option;
2797                 oldi = i;
2798                 switch ((unsigned char) buf[++i]) {
2799                   case TN_WILL:
2800                     if (appData.debugMode)
2801                       fprintf(debugFP, "\n<WILL ");
2802                     switch (option = (unsigned char) buf[++i]) {
2803                       case TN_ECHO:
2804                         if (appData.debugMode)
2805                           fprintf(debugFP, "ECHO ");
2806                         /* Reply only if this is a change, according
2807                            to the protocol rules. */
2808                         if (remoteEchoOption) break;
2809                         if (appData.localLineEditing &&
2810                             atoi(appData.icsPort) == TN_PORT) {
2811                             TelnetRequest(TN_DONT, TN_ECHO);
2812                         } else {
2813                             EchoOff();
2814                             TelnetRequest(TN_DO, TN_ECHO);
2815                             remoteEchoOption = TRUE;
2816                         }
2817                         break;
2818                       default:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "%d ", option);
2821                         /* Whatever this is, we don't want it. */
2822                         TelnetRequest(TN_DONT, option);
2823                         break;
2824                     }
2825                     break;
2826                   case TN_WONT:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<WONT ");
2829                     switch (option = (unsigned char) buf[++i]) {
2830                       case TN_ECHO:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "ECHO ");
2833                         /* Reply only if this is a change, according
2834                            to the protocol rules. */
2835                         if (!remoteEchoOption) break;
2836                         EchoOn();
2837                         TelnetRequest(TN_DONT, TN_ECHO);
2838                         remoteEchoOption = FALSE;
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", (unsigned char) option);
2843                         /* Whatever this is, it must already be turned
2844                            off, because we never agree to turn on
2845                            anything non-default, so according to the
2846                            protocol rules, we don't reply. */
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DO:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DO ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         /* Whatever this is, we refuse to do it. */
2856                         if (appData.debugMode)
2857                           fprintf(debugFP, "%d ", option);
2858                         TelnetRequest(TN_WONT, option);
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DONT:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DONT ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         if (appData.debugMode)
2868                           fprintf(debugFP, "%d ", option);
2869                         /* Whatever this is, we are already not doing
2870                            it, because we never agree to do anything
2871                            non-default, so according to the protocol
2872                            rules, we don't reply. */
2873                         break;
2874                     }
2875                     break;
2876                   case TN_IAC:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<IAC ");
2879                     /* Doubled IAC; pass it through */
2880                     i--;
2881                     break;
2882                   default:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2885                     /* Drop all other telnet commands on the floor */
2886                     break;
2887                 }
2888                 if (oldi > next_out)
2889                   SendToPlayer(&buf[next_out], oldi - next_out);
2890                 if (++i > next_out)
2891                   next_out = i;
2892                 continue;
2893             }
2894
2895             /* OK, this at least will *usually* work */
2896             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2897                 loggedOn = TRUE;
2898             }
2899
2900             if (loggedOn && !intfSet) {
2901                 if (ics_type == ICS_ICC) {
2902                   snprintf(str, MSG_SIZ,
2903                           "/set-quietly interface %s\n/set-quietly style 12\n",
2904                           programVersion);
2905                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2906                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2907                 } else if (ics_type == ICS_CHESSNET) {
2908                   snprintf(str, MSG_SIZ, "/style 12\n");
2909                 } else {
2910                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2911                   strcat(str, programVersion);
2912                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2915 #ifdef WIN32
2916                   strcat(str, "$iset nohighlight 1\n");
2917 #endif
2918                   strcat(str, "$iset lock 1\n$style 12\n");
2919                 }
2920                 SendToICS(str);
2921                 NotifyFrontendLogin();
2922                 intfSet = TRUE;
2923             }
2924
2925             if (started == STARTED_COMMENT) {
2926                 /* Accumulate characters in comment */
2927                 parse[parse_pos++] = buf[i];
2928                 if (buf[i] == '\n') {
2929                     parse[parse_pos] = NULLCHAR;
2930                     if(chattingPartner>=0) {
2931                         char mess[MSG_SIZ];
2932                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2933                         OutputChatMessage(chattingPartner, mess);
2934                         chattingPartner = -1;
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     } else
2937                     if(!suppressKibitz) // [HGM] kibitz
2938                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2939                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2940                         int nrDigit = 0, nrAlph = 0, j;
2941                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2942                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2943                         parse[parse_pos] = NULLCHAR;
2944                         // try to be smart: if it does not look like search info, it should go to
2945                         // ICS interaction window after all, not to engine-output window.
2946                         for(j=0; j<parse_pos; j++) { // count letters and digits
2947                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2948                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2949                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2950                         }
2951                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2952                             int depth=0; float score;
2953                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2954                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2955                                 pvInfoList[forwardMostMove-1].depth = depth;
2956                                 pvInfoList[forwardMostMove-1].score = 100*score;
2957                             }
2958                             OutputKibitz(suppressKibitz, parse);
2959                         } else {
2960                             char tmp[MSG_SIZ];
2961                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2962                             SendToPlayer(tmp, strlen(tmp));
2963                         }
2964                         next_out = i+1; // [HGM] suppress printing in ICS window
2965                     }
2966                     started = STARTED_NONE;
2967                 } else {
2968                     /* Don't match patterns against characters in comment */
2969                     i++;
2970                     continue;
2971                 }
2972             }
2973             if (started == STARTED_CHATTER) {
2974                 if (buf[i] != '\n') {
2975                     /* Don't match patterns against characters in chatter */
2976                     i++;
2977                     continue;
2978                 }
2979                 started = STARTED_NONE;
2980                 if(suppressKibitz) next_out = i+1;
2981             }
2982
2983             /* Kludge to deal with rcmd protocol */
2984             if (firstTime && looking_at(buf, &i, "\001*")) {
2985                 DisplayFatalError(&buf[1], 0, 1);
2986                 continue;
2987             } else {
2988                 firstTime = FALSE;
2989             }
2990
2991             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2992                 ics_type = ICS_ICC;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2999                 ics_type = ICS_FICS;
3000                 ics_prefix = "$";
3001                 if (appData.debugMode)
3002                   fprintf(debugFP, "ics_type %d\n", ics_type);
3003                 continue;
3004             }
3005             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3006                 ics_type = ICS_CHESSNET;
3007                 ics_prefix = "/";
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, "ics_type %d\n", ics_type);
3010                 continue;
3011             }
3012
3013             if (!loggedOn &&
3014                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3015                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3016                  looking_at(buf, &i, "will be \"*\""))) {
3017               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3018               continue;
3019             }
3020
3021             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3022               char buf[MSG_SIZ];
3023               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3024               DisplayIcsInteractionTitle(buf);
3025               have_set_title = TRUE;
3026             }
3027
3028             /* skip finger notes */
3029             if (started == STARTED_NONE &&
3030                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3031                  (buf[i] == '1' && buf[i+1] == '0')) &&
3032                 buf[i+2] == ':' && buf[i+3] == ' ') {
3033               started = STARTED_CHATTER;
3034               i += 3;
3035               continue;
3036             }
3037
3038             oldi = i;
3039             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3040             if(appData.seekGraph) {
3041                 if(soughtPending && MatchSoughtLine(buf+i)) {
3042                     i = strstr(buf+i, "rated") - buf;
3043                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044                     next_out = leftover_start = i;
3045                     started = STARTED_CHATTER;
3046                     suppressKibitz = TRUE;
3047                     continue;
3048                 }
3049                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3050                         && looking_at(buf, &i, "* ads displayed")) {
3051                     soughtPending = FALSE;
3052                     seekGraphUp = TRUE;
3053                     DrawSeekGraph();
3054                     continue;
3055                 }
3056                 if(appData.autoRefresh) {
3057                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3058                         int s = (ics_type == ICS_ICC); // ICC format differs
3059                         if(seekGraphUp)
3060                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3061                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3064                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                         next_out = i; // suppress
3066                         continue;
3067                     }
3068                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3069                         char *p = star_match[0];
3070                         while(*p) {
3071                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3072                             while(*p && *p++ != ' '); // next
3073                         }
3074                         looking_at(buf, &i, "*% "); // eat prompt
3075                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                         next_out = i;
3077                         continue;
3078                     }
3079                 }
3080             }
3081
3082             /* skip formula vars */
3083             if (started == STARTED_NONE &&
3084                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3091             if (appData.autoKibitz && started == STARTED_NONE &&
3092                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3093                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3094                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3095                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3096                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3097                         suppressKibitz = TRUE;
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3101                                 && (gameMode == IcsPlayingWhite)) ||
3102                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3103                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3104                             started = STARTED_CHATTER; // own kibitz we simply discard
3105                         else {
3106                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3107                             parse_pos = 0; parse[0] = NULLCHAR;
3108                             savingComment = TRUE;
3109                             suppressKibitz = gameMode != IcsObserving ? 2 :
3110                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3111                         }
3112                         continue;
3113                 } else
3114                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3115                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3116                          && atoi(star_match[0])) {
3117                     // suppress the acknowledgements of our own autoKibitz
3118                     char *p;
3119                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3121                     SendToPlayer(star_match[0], strlen(star_match[0]));
3122                     if(looking_at(buf, &i, "*% ")) // eat prompt
3123                         suppressKibitz = FALSE;
3124                     next_out = i;
3125                     continue;
3126                 }
3127             } // [HGM] kibitz: end of patch
3128
3129             // [HGM] chat: intercept tells by users for which we have an open chat window
3130             channel = -1;
3131             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3132                                            looking_at(buf, &i, "* whispers:") ||
3133                                            looking_at(buf, &i, "* kibitzes:") ||
3134                                            looking_at(buf, &i, "* shouts:") ||
3135                                            looking_at(buf, &i, "* c-shouts:") ||
3136                                            looking_at(buf, &i, "--> * ") ||
3137                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3140                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3141                 int p;
3142                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3143                 chattingPartner = -1;
3144
3145                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3146                 for(p=0; p<MAX_CHAT; p++) {
3147                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3148                     talker[0] = '['; strcat(talker, "] ");
3149                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3150                     chattingPartner = p; break;
3151                     }
3152                 } else
3153                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(!strcmp("kibitzes", chatPartner[p])) {
3156                         talker[0] = '['; strcat(talker, "] ");
3157                         chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("whispers", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3168                   if(buf[i-8] == '-' && buf[i-3] == 't')
3169                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3170                     if(!strcmp("c-shouts", chatPartner[p])) {
3171                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3172                         chattingPartner = p; break;
3173                     }
3174                   }
3175                   if(chattingPartner < 0)
3176                   for(p=0; p<MAX_CHAT; p++) {
3177                     if(!strcmp("shouts", chatPartner[p])) {
3178                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3179                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3180                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                 }
3185                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3186                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3187                     talker[0] = 0; Colorize(ColorTell, FALSE);
3188                     chattingPartner = p; break;
3189                 }
3190                 if(chattingPartner<0) i = oldi; else {
3191                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3192                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3193                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194                     started = STARTED_COMMENT;
3195                     parse_pos = 0; parse[0] = NULLCHAR;
3196                     savingComment = 3 + chattingPartner; // counts as TRUE
3197                     suppressKibitz = TRUE;
3198                     continue;
3199                 }
3200             } // [HGM] chat: end of patch
3201
3202           backup = i;
3203             if (appData.zippyTalk || appData.zippyPlay) {
3204                 /* [DM] Backup address for color zippy lines */
3205 #if ZIPPY
3206                if (loggedOn == TRUE)
3207                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3208                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3209 #endif
3210             } // [DM] 'else { ' deleted
3211                 if (
3212                     /* Regular tells and says */
3213                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3214                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3215                     looking_at(buf, &i, "* says: ") ||
3216                     /* Don't color "message" or "messages" output */
3217                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3218                     looking_at(buf, &i, "*. * at *:*: ") ||
3219                     looking_at(buf, &i, "--* (*:*): ") ||
3220                     /* Message notifications (same color as tells) */
3221                     looking_at(buf, &i, "* has left a message ") ||
3222                     looking_at(buf, &i, "* just sent you a message:\n") ||
3223                     /* Whispers and kibitzes */
3224                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3225                     looking_at(buf, &i, "* kibitzes: ") ||
3226                     /* Channel tells */
3227                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3228
3229                   if (tkind == 1 && strchr(star_match[0], ':')) {
3230                       /* Avoid "tells you:" spoofs in channels */
3231                      tkind = 3;
3232                   }
3233                   if (star_match[0][0] == NULLCHAR ||
3234                       strchr(star_match[0], ' ') ||
3235                       (tkind == 3 && strchr(star_match[1], ' '))) {
3236                     /* Reject bogus matches */
3237                     i = oldi;
3238                   } else {
3239                     if (appData.colorize) {
3240                       if (oldi > next_out) {
3241                         SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = oldi;
3243                       }
3244                       switch (tkind) {
3245                       case 1:
3246                         Colorize(ColorTell, FALSE);
3247                         curColor = ColorTell;
3248                         break;
3249                       case 2:
3250                         Colorize(ColorKibitz, FALSE);
3251                         curColor = ColorKibitz;
3252                         break;
3253                       case 3:
3254                         p = strrchr(star_match[1], '(');
3255                         if (p == NULL) {
3256                           p = star_match[1];
3257                         } else {
3258                           p++;
3259                         }
3260                         if (atoi(p) == 1) {
3261                           Colorize(ColorChannel1, FALSE);
3262                           curColor = ColorChannel1;
3263                         } else {
3264                           Colorize(ColorChannel, FALSE);
3265                           curColor = ColorChannel;
3266                         }
3267                         break;
3268                       case 5:
3269                         curColor = ColorNormal;
3270                         break;
3271                       }
3272                     }
3273                     if (started == STARTED_NONE && appData.autoComment &&
3274                         (gameMode == IcsObserving ||
3275                          gameMode == IcsPlayingWhite ||
3276                          gameMode == IcsPlayingBlack)) {
3277                       parse_pos = i - oldi;
3278                       memcpy(parse, &buf[oldi], parse_pos);
3279                       parse[parse_pos] = NULLCHAR;
3280                       started = STARTED_COMMENT;
3281                       savingComment = TRUE;
3282                     } else {
3283                       started = STARTED_CHATTER;
3284                       savingComment = FALSE;
3285                     }
3286                     loggedOn = TRUE;
3287                     continue;
3288                   }
3289                 }
3290
3291                 if (looking_at(buf, &i, "* s-shouts: ") ||
3292                     looking_at(buf, &i, "* c-shouts: ")) {
3293                     if (appData.colorize) {
3294                         if (oldi > next_out) {
3295                             SendToPlayer(&buf[next_out], oldi - next_out);
3296                             next_out = oldi;
3297                         }
3298                         Colorize(ColorSShout, FALSE);
3299                         curColor = ColorSShout;
3300                     }
3301                     loggedOn = TRUE;
3302                     started = STARTED_CHATTER;
3303                     continue;
3304                 }
3305
3306                 if (looking_at(buf, &i, "--->")) {
3307                     loggedOn = TRUE;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* shouts: ") ||
3312                     looking_at(buf, &i, "--> ")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorShout, FALSE);
3319                         curColor = ColorShout;
3320                     }
3321                     loggedOn = TRUE;
3322                     started = STARTED_CHATTER;
3323                     continue;
3324                 }
3325
3326                 if (looking_at( buf, &i, "Challenge:")) {
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorChallenge, FALSE);
3333                         curColor = ColorChallenge;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                 }
3338
3339                 if (looking_at(buf, &i, "* offers you") ||
3340                     looking_at(buf, &i, "* offers to be") ||
3341                     looking_at(buf, &i, "* would like to") ||
3342                     looking_at(buf, &i, "* requests to") ||
3343                     looking_at(buf, &i, "Your opponent offers") ||
3344                     looking_at(buf, &i, "Your opponent requests")) {
3345
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorRequest, FALSE);
3352                         curColor = ColorRequest;
3353                     }
3354                     continue;
3355                 }
3356
3357                 if (looking_at(buf, &i, "* (*) seeking")) {
3358                     if (appData.colorize) {
3359                         if (oldi > next_out) {
3360                             SendToPlayer(&buf[next_out], oldi - next_out);
3361                             next_out = oldi;
3362                         }
3363                         Colorize(ColorSeek, FALSE);
3364                         curColor = ColorSeek;
3365                     }
3366                     continue;
3367             }
3368
3369           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3370
3371             if (looking_at(buf, &i, "\\   ")) {
3372                 if (prevColor != ColorNormal) {
3373                     if (oldi > next_out) {
3374                         SendToPlayer(&buf[next_out], oldi - next_out);
3375                         next_out = oldi;
3376                     }
3377                     Colorize(prevColor, TRUE);
3378                     curColor = prevColor;
3379                 }
3380                 if (savingComment) {
3381                     parse_pos = i - oldi;
3382                     memcpy(parse, &buf[oldi], parse_pos);
3383                     parse[parse_pos] = NULLCHAR;
3384                     started = STARTED_COMMENT;
3385                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3386                         chattingPartner = savingComment - 3; // kludge to remember the box
3387                 } else {
3388                     started = STARTED_CHATTER;
3389                 }
3390                 continue;
3391             }
3392
3393             if (looking_at(buf, &i, "Black Strength :") ||
3394                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3395                 looking_at(buf, &i, "<10>") ||
3396                 looking_at(buf, &i, "#@#")) {
3397                 /* Wrong board style */
3398                 loggedOn = TRUE;
3399                 SendToICS(ics_prefix);
3400                 SendToICS("set style 12\n");
3401                 SendToICS(ics_prefix);
3402                 SendToICS("refresh\n");
3403                 continue;
3404             }
3405
3406             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3407                 ICSInitScript();
3408                 have_sent_ICS_logon = 1;
3409                 continue;
3410             }
3411
3412             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3413                 (looking_at(buf, &i, "\n<12> ") ||
3414                  looking_at(buf, &i, "<12> "))) {
3415                 loggedOn = TRUE;
3416                 if (oldi > next_out) {
3417                     SendToPlayer(&buf[next_out], oldi - next_out);
3418                 }
3419                 next_out = i;
3420                 started = STARTED_BOARD;
3421                 parse_pos = 0;
3422                 continue;
3423             }
3424
3425             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3426                 looking_at(buf, &i, "<b1> ")) {
3427                 if (oldi > next_out) {
3428                     SendToPlayer(&buf[next_out], oldi - next_out);
3429                 }
3430                 next_out = i;
3431                 started = STARTED_HOLDINGS;
3432                 parse_pos = 0;
3433                 continue;
3434             }
3435
3436             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3437                 loggedOn = TRUE;
3438                 /* Header for a move list -- first line */
3439
3440                 switch (ics_getting_history) {
3441                   case H_FALSE:
3442                     switch (gameMode) {
3443                       case IcsIdle:
3444                       case BeginningOfGame:
3445                         /* User typed "moves" or "oldmoves" while we
3446                            were idle.  Pretend we asked for these
3447                            moves and soak them up so user can step
3448                            through them and/or save them.
3449                            */
3450                         Reset(FALSE, TRUE);
3451                         gameMode = IcsObserving;
3452                         ModeHighlight();
3453                         ics_gamenum = -1;
3454                         ics_getting_history = H_GOT_UNREQ_HEADER;
3455                         break;
3456                       case EditGame: /*?*/
3457                       case EditPosition: /*?*/
3458                         /* Should above feature work in these modes too? */
3459                         /* For now it doesn't */
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                       default:
3463                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3464                         break;
3465                     }
3466                     break;
3467                   case H_REQUESTED:
3468                     /* Is this the right one? */
3469                     if (gameInfo.white && gameInfo.black &&
3470                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3471                         strcmp(gameInfo.black, star_match[2]) == 0) {
3472                         /* All is well */
3473                         ics_getting_history = H_GOT_REQ_HEADER;
3474                     }
3475                     break;
3476                   case H_GOT_REQ_HEADER:
3477                   case H_GOT_UNREQ_HEADER:
3478                   case H_GOT_UNWANTED_HEADER:
3479                   case H_GETTING_MOVES:
3480                     /* Should not happen */
3481                     DisplayError(_("Error gathering move list: two headers"), 0);
3482                     ics_getting_history = H_FALSE;
3483                     break;
3484                 }
3485
3486                 /* Save player ratings into gameInfo if needed */
3487                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3488                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3489                     (gameInfo.whiteRating == -1 ||
3490                      gameInfo.blackRating == -1)) {
3491
3492                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3493                     gameInfo.blackRating = string_to_rating(star_match[3]);
3494                     if (appData.debugMode)
3495                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3496                               gameInfo.whiteRating, gameInfo.blackRating);
3497                 }
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i,
3502               "* * match, initial time: * minute*, increment: * second")) {
3503                 /* Header for a move list -- second line */
3504                 /* Initial board will follow if this is a wild game */
3505                 if (gameInfo.event != NULL) free(gameInfo.event);
3506                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3507                 gameInfo.event = StrSave(str);
3508                 /* [HGM] we switched variant. Translate boards if needed. */
3509                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3510                 continue;
3511             }
3512
3513             if (looking_at(buf, &i, "Move  ")) {
3514                 /* Beginning of a move list */
3515                 switch (ics_getting_history) {
3516                   case H_FALSE:
3517                     /* Normally should not happen */
3518                     /* Maybe user hit reset while we were parsing */
3519                     break;
3520                   case H_REQUESTED:
3521                     /* Happens if we are ignoring a move list that is not
3522                      * the one we just requested.  Common if the user
3523                      * tries to observe two games without turning off
3524                      * getMoveList */
3525                     break;
3526                   case H_GETTING_MOVES:
3527                     /* Should not happen */
3528                     DisplayError(_("Error gathering move list: nested"), 0);
3529                     ics_getting_history = H_FALSE;
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                     ics_getting_history = H_GETTING_MOVES;
3533                     started = STARTED_MOVES;
3534                     parse_pos = 0;
3535                     if (oldi > next_out) {
3536                         SendToPlayer(&buf[next_out], oldi - next_out);
3537                     }
3538                     break;
3539                   case H_GOT_UNREQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES_NOHIDE;
3542                     parse_pos = 0;
3543                     break;
3544                   case H_GOT_UNWANTED_HEADER:
3545                     ics_getting_history = H_FALSE;
3546                     break;
3547                 }
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "% ") ||
3552                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3553                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3554                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3555                     soughtPending = FALSE;
3556                     seekGraphUp = TRUE;
3557                     DrawSeekGraph();
3558                 }
3559                 if(suppressKibitz) next_out = i;
3560                 savingComment = FALSE;
3561                 suppressKibitz = 0;
3562                 switch (started) {
3563                   case STARTED_MOVES:
3564                   case STARTED_MOVES_NOHIDE:
3565                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3566                     parse[parse_pos + i - oldi] = NULLCHAR;
3567                     ParseGameHistory(parse);
3568 #if ZIPPY
3569                     if (appData.zippyPlay && first.initDone) {
3570                         FeedMovesToProgram(&first, forwardMostMove);
3571                         if (gameMode == IcsPlayingWhite) {
3572                             if (WhiteOnMove(forwardMostMove)) {
3573                                 if (first.sendTime) {
3574                                   if (first.useColors) {
3575                                     SendToProgram("black\n", &first);
3576                                   }
3577                                   SendTimeRemaining(&first, TRUE);
3578                                 }
3579                                 if (first.useColors) {
3580                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3581                                 }
3582                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3583                                 first.maybeThinking = TRUE;
3584                             } else {
3585                                 if (first.usePlayother) {
3586                                   if (first.sendTime) {
3587                                     SendTimeRemaining(&first, TRUE);
3588                                   }
3589                                   SendToProgram("playother\n", &first);
3590                                   firstMove = FALSE;
3591                                 } else {
3592                                   firstMove = TRUE;
3593                                 }
3594                             }
3595                         } else if (gameMode == IcsPlayingBlack) {
3596                             if (!WhiteOnMove(forwardMostMove)) {
3597                                 if (first.sendTime) {
3598                                   if (first.useColors) {
3599                                     SendToProgram("white\n", &first);
3600                                   }
3601                                   SendTimeRemaining(&first, FALSE);
3602                                 }
3603                                 if (first.useColors) {
3604                                   SendToProgram("black\n", &first);
3605                                 }
3606                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3607                                 first.maybeThinking = TRUE;
3608                             } else {
3609                                 if (first.usePlayother) {
3610                                   if (first.sendTime) {
3611                                     SendTimeRemaining(&first, FALSE);
3612                                   }
3613                                   SendToProgram("playother\n", &first);
3614                                   firstMove = FALSE;
3615                                 } else {
3616                                   firstMove = TRUE;
3617                                 }
3618                             }
3619                         }
3620                     }
3621 #endif
3622                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3623                         /* Moves came from oldmoves or moves command
3624                            while we weren't doing anything else.
3625                            */
3626                         currentMove = forwardMostMove;
3627                         ClearHighlights();/*!!could figure this out*/
3628                         flipView = appData.flipView;
3629                         DrawPosition(TRUE, boards[currentMove]);
3630                         DisplayBothClocks();
3631                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3632                                 gameInfo.white, gameInfo.black);
3633                         DisplayTitle(str);
3634                         gameMode = IcsIdle;
3635                     } else {
3636                         /* Moves were history of an active game */
3637                         if (gameInfo.resultDetails != NULL) {
3638                             free(gameInfo.resultDetails);
3639                             gameInfo.resultDetails = NULL;
3640                         }
3641                     }
3642                     HistorySet(parseList, backwardMostMove,
3643                                forwardMostMove, currentMove-1);
3644                     DisplayMove(currentMove - 1);
3645                     if (started == STARTED_MOVES) next_out = i;
3646                     started = STARTED_NONE;
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649
3650                   case STARTED_OBSERVE:
3651                     started = STARTED_NONE;
3652                     SendToICS(ics_prefix);
3653                     SendToICS("refresh\n");
3654                     break;
3655
3656                   default:
3657                     break;
3658                 }
3659                 if(bookHit) { // [HGM] book: simulate book reply
3660                     static char bookMove[MSG_SIZ]; // a bit generous?
3661
3662                     programStats.nodes = programStats.depth = programStats.time =
3663                     programStats.score = programStats.got_only_move = 0;
3664                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3665
3666                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3667                     strcat(bookMove, bookHit);
3668                     HandleMachineMove(bookMove, &first);
3669                 }
3670                 continue;
3671             }
3672
3673             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3674                  started == STARTED_HOLDINGS ||
3675                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3676                 /* Accumulate characters in move list or board */
3677                 parse[parse_pos++] = buf[i];
3678             }
3679
3680             /* Start of game messages.  Mostly we detect start of game
3681                when the first board image arrives.  On some versions
3682                of the ICS, though, we need to do a "refresh" after starting
3683                to observe in order to get the current board right away. */
3684             if (looking_at(buf, &i, "Adding game * to observation list")) {
3685                 started = STARTED_OBSERVE;
3686                 continue;
3687             }
3688
3689             /* Handle auto-observe */
3690             if (appData.autoObserve &&
3691                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3692                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3693                 char *player;
3694                 /* Choose the player that was highlighted, if any. */
3695                 if (star_match[0][0] == '\033' ||
3696                     star_match[1][0] != '\033') {
3697                     player = star_match[0];
3698                 } else {
3699                     player = star_match[2];
3700                 }
3701                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3702                         ics_prefix, StripHighlightAndTitle(player));
3703                 SendToICS(str);
3704
3705                 /* Save ratings from notify string */
3706                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3707                 player1Rating = string_to_rating(star_match[1]);
3708                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3709                 player2Rating = string_to_rating(star_match[3]);
3710
3711                 if (appData.debugMode)
3712                   fprintf(debugFP,
3713                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3714                           player1Name, player1Rating,
3715                           player2Name, player2Rating);
3716
3717                 continue;
3718             }
3719
3720             /* Deal with automatic examine mode after a game,
3721                and with IcsObserving -> IcsExamining transition */
3722             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3723                 looking_at(buf, &i, "has made you an examiner of game *")) {
3724
3725                 int gamenum = atoi(star_match[0]);
3726                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3727                     gamenum == ics_gamenum) {
3728                     /* We were already playing or observing this game;
3729                        no need to refetch history */
3730                     gameMode = IcsExamining;
3731                     if (pausing) {
3732                         pauseExamForwardMostMove = forwardMostMove;
3733                     } else if (currentMove < forwardMostMove) {
3734                         ForwardInner(forwardMostMove);
3735                     }
3736                 } else {
3737                     /* I don't think this case really can happen */
3738                     SendToICS(ics_prefix);
3739                     SendToICS("refresh\n");
3740                 }
3741                 continue;
3742             }
3743
3744             /* Error messages */
3745 //          if (ics_user_moved) {
3746             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3747                 if (looking_at(buf, &i, "Illegal move") ||
3748                     looking_at(buf, &i, "Not a legal move") ||
3749                     looking_at(buf, &i, "Your king is in check") ||
3750                     looking_at(buf, &i, "It isn't your turn") ||
3751                     looking_at(buf, &i, "It is not your move")) {
3752                     /* Illegal move */
3753                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3754                         currentMove = forwardMostMove-1;
3755                         DisplayMove(currentMove - 1); /* before DMError */
3756                         DrawPosition(FALSE, boards[currentMove]);
3757                         SwitchClocks(forwardMostMove-1); // [HGM] race
3758                         DisplayBothClocks();
3759                     }
3760                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3761                     ics_user_moved = 0;
3762                     continue;
3763                 }
3764             }
3765
3766             if (looking_at(buf, &i, "still have time") ||
3767                 looking_at(buf, &i, "not out of time") ||
3768                 looking_at(buf, &i, "either player is out of time") ||
3769                 looking_at(buf, &i, "has timeseal; checking")) {
3770                 /* We must have called his flag a little too soon */
3771                 whiteFlag = blackFlag = FALSE;
3772                 continue;
3773             }
3774
3775             if (looking_at(buf, &i, "added * seconds to") ||
3776                 looking_at(buf, &i, "seconds were added to")) {
3777                 /* Update the clocks */
3778                 SendToICS(ics_prefix);
3779                 SendToICS("refresh\n");
3780                 continue;
3781             }
3782
3783             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3784                 ics_clock_paused = TRUE;
3785                 StopClocks();
3786                 continue;
3787             }
3788
3789             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3790                 ics_clock_paused = FALSE;
3791                 StartClocks();
3792                 continue;
3793             }
3794
3795             /* Grab player ratings from the Creating: message.
3796                Note we have to check for the special case when
3797                the ICS inserts things like [white] or [black]. */
3798             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3799                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3800                 /* star_matches:
3801                    0    player 1 name (not necessarily white)
3802                    1    player 1 rating
3803                    2    empty, white, or black (IGNORED)
3804                    3    player 2 name (not necessarily black)
3805                    4    player 2 rating
3806
3807                    The names/ratings are sorted out when the game
3808                    actually starts (below).
3809                 */
3810                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3811                 player1Rating = string_to_rating(star_match[1]);
3812                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3813                 player2Rating = string_to_rating(star_match[4]);
3814
3815                 if (appData.debugMode)
3816                   fprintf(debugFP,
3817                           "Ratings from 'Creating:' %s %d, %s %d\n",
3818                           player1Name, player1Rating,
3819                           player2Name, player2Rating);
3820
3821                 continue;
3822             }
3823
3824             /* Improved generic start/end-of-game messages */
3825             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3826                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3827                 /* If tkind == 0: */
3828                 /* star_match[0] is the game number */
3829                 /*           [1] is the white player's name */
3830                 /*           [2] is the black player's name */
3831                 /* For end-of-game: */
3832                 /*           [3] is the reason for the game end */
3833                 /*           [4] is a PGN end game-token, preceded by " " */
3834                 /* For start-of-game: */
3835                 /*           [3] begins with "Creating" or "Continuing" */
3836                 /*           [4] is " *" or empty (don't care). */
3837                 int gamenum = atoi(star_match[0]);
3838                 char *whitename, *blackname, *why, *endtoken;
3839                 ChessMove endtype = EndOfFile;
3840
3841                 if (tkind == 0) {
3842                   whitename = star_match[1];
3843                   blackname = star_match[2];
3844                   why = star_match[3];
3845                   endtoken = star_match[4];
3846                 } else {
3847                   whitename = star_match[1];
3848                   blackname = star_match[3];
3849                   why = star_match[5];
3850                   endtoken = star_match[6];
3851                 }
3852
3853                 /* Game start messages */
3854                 if (strncmp(why, "Creating ", 9) == 0 ||
3855                     strncmp(why, "Continuing ", 11) == 0) {
3856                     gs_gamenum = gamenum;
3857                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3858                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3859                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3860 #if ZIPPY
3861                     if (appData.zippyPlay) {
3862                         ZippyGameStart(whitename, blackname);
3863                     }
3864 #endif /*ZIPPY*/
3865                     partnerBoardValid = FALSE; // [HGM] bughouse
3866                     continue;
3867                 }
3868
3869                 /* Game end messages */
3870                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3871                     ics_gamenum != gamenum) {
3872                     continue;
3873                 }
3874                 while (endtoken[0] == ' ') endtoken++;
3875                 switch (endtoken[0]) {
3876                   case '*':
3877                   default:
3878                     endtype = GameUnfinished;
3879                     break;
3880                   case '0':
3881                     endtype = BlackWins;
3882                     break;
3883                   case '1':
3884                     if (endtoken[1] == '/')
3885                       endtype = GameIsDrawn;
3886                     else
3887                       endtype = WhiteWins;
3888                     break;
3889                 }
3890                 GameEnds(endtype, why, GE_ICS);
3891 #if ZIPPY
3892                 if (appData.zippyPlay && first.initDone) {
3893                     ZippyGameEnd(endtype, why);
3894                     if (first.pr == NoProc) {
3895                       /* Start the next process early so that we'll
3896                          be ready for the next challenge */
3897                       StartChessProgram(&first);
3898                     }
3899                     /* Send "new" early, in case this command takes
3900                        a long time to finish, so that we'll be ready
3901                        for the next challenge. */
3902                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3903                     Reset(TRUE, TRUE);
3904                 }
3905 #endif /*ZIPPY*/
3906                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3907                 continue;
3908             }
3909
3910             if (looking_at(buf, &i, "Removing game * from observation") ||
3911                 looking_at(buf, &i, "no longer observing game *") ||
3912                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3913                 if (gameMode == IcsObserving &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       /* icsEngineAnalyze */
3917                       if (appData.icsEngineAnalyze) {
3918                             ExitAnalyzeMode();
3919                             ModeHighlight();
3920                       }
3921                       StopClocks();
3922                       gameMode = IcsIdle;
3923                       ics_gamenum = -1;
3924                       ics_user_moved = FALSE;
3925                   }
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "no longer examining game *")) {
3930                 if (gameMode == IcsExamining &&
3931                     atoi(star_match[0]) == ics_gamenum)
3932                   {
3933                       gameMode = IcsIdle;
3934                       ics_gamenum = -1;
3935                       ics_user_moved = FALSE;
3936                   }
3937                 continue;
3938             }
3939
3940             /* Advance leftover_start past any newlines we find,
3941                so only partial lines can get reparsed */
3942             if (looking_at(buf, &i, "\n")) {
3943                 prevColor = curColor;
3944                 if (curColor != ColorNormal) {
3945                     if (oldi > next_out) {
3946                         SendToPlayer(&buf[next_out], oldi - next_out);
3947                         next_out = oldi;
3948                     }
3949                     Colorize(ColorNormal, FALSE);
3950                     curColor = ColorNormal;
3951                 }
3952                 if (started == STARTED_BOARD) {
3953                     started = STARTED_NONE;
3954                     parse[parse_pos] = NULLCHAR;
3955                     ParseBoard12(parse);
3956                     ics_user_moved = 0;
3957
3958                     /* Send premove here */
3959                     if (appData.premove) {
3960                       char str[MSG_SIZ];
3961                       if (currentMove == 0 &&
3962                           gameMode == IcsPlayingWhite &&
3963                           appData.premoveWhite) {
3964                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                         SendToICS(str);
3968                       } else if (currentMove == 1 &&
3969                                  gameMode == IcsPlayingBlack &&
3970                                  appData.premoveBlack) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (gotPremove) {
3976                         gotPremove = 0;
3977                         ClearPremoveHighlights();
3978                         if (appData.debugMode)
3979                           fprintf(debugFP, "Sending premove:\n");
3980                           UserMoveEvent(premoveFromX, premoveFromY,
3981                                         premoveToX, premoveToY,
3982                                         premovePromoChar);
3983                       }
3984                     }
3985
3986                     /* Usually suppress following prompt */
3987                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3988                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3989                         if (looking_at(buf, &i, "*% ")) {
3990                             savingComment = FALSE;
3991                             suppressKibitz = 0;
3992                         }
3993                     }
3994                     next_out = i;
3995                 } else if (started == STARTED_HOLDINGS) {
3996                     int gamenum;
3997                     char new_piece[MSG_SIZ];
3998                     started = STARTED_NONE;
3999                     parse[parse_pos] = NULLCHAR;
4000                     if (appData.debugMode)
4001                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4002                                                         parse, currentMove);
4003                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4004                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4005                         if (gameInfo.variant == VariantNormal) {
4006                           /* [HGM] We seem to switch variant during a game!
4007                            * Presumably no holdings were displayed, so we have
4008                            * to move the position two files to the right to
4009                            * create room for them!
4010                            */
4011                           VariantClass newVariant;
4012                           switch(gameInfo.boardWidth) { // base guess on board width
4013                                 case 9:  newVariant = VariantShogi; break;
4014                                 case 10: newVariant = VariantGreat; break;
4015                                 default: newVariant = VariantCrazyhouse; break;
4016                           }
4017                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4018                           /* Get a move list just to see the header, which
4019                              will tell us whether this is really bug or zh */
4020                           if (ics_getting_history == H_FALSE) {
4021                             ics_getting_history = H_REQUESTED;
4022                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023                             SendToICS(str);
4024                           }
4025                         }
4026                         new_piece[0] = NULLCHAR;
4027                         sscanf(parse, "game %d white [%s black [%s <- %s",
4028                                &gamenum, white_holding, black_holding,
4029                                new_piece);
4030                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4031                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4032                         /* [HGM] copy holdings to board holdings area */
4033                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4034                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4035                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4036 #if ZIPPY
4037                         if (appData.zippyPlay && first.initDone) {
4038                             ZippyHoldings(white_holding, black_holding,
4039                                           new_piece);
4040                         }
4041 #endif /*ZIPPY*/
4042                         if (tinyLayout || smallLayout) {
4043                             char wh[16], bh[16];
4044                             PackHolding(wh, white_holding);
4045                             PackHolding(bh, black_holding);
4046                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4047                                     gameInfo.white, gameInfo.black);
4048                         } else {
4049                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4050                                     gameInfo.white, white_holding,
4051                                     gameInfo.black, black_holding);
4052                         }
4053                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4054                         DrawPosition(FALSE, boards[currentMove]);
4055                         DisplayTitle(str);
4056                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4057                         sscanf(parse, "game %d white [%s black [%s <- %s",
4058                                &gamenum, white_holding, black_holding,
4059                                new_piece);
4060                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4061                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4062                         /* [HGM] copy holdings to partner-board holdings area */
4063                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4064                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4065                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4066                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4067                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4068                       }
4069                     }
4070                     /* Suppress following prompt */
4071                     if (looking_at(buf, &i, "*% ")) {
4072                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4073                         savingComment = FALSE;
4074                         suppressKibitz = 0;
4075                     }
4076                     next_out = i;
4077                 }
4078                 continue;
4079             }
4080
4081             i++;                /* skip unparsed character and loop back */
4082         }
4083
4084         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4085 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4086 //          SendToPlayer(&buf[next_out], i - next_out);
4087             started != STARTED_HOLDINGS && leftover_start > next_out) {
4088             SendToPlayer(&buf[next_out], leftover_start - next_out);
4089             next_out = i;
4090         }
4091
4092         leftover_len = buf_len - leftover_start;
4093         /* if buffer ends with something we couldn't parse,
4094            reparse it after appending the next read */
4095
4096     } else if (count == 0) {
4097         RemoveInputSource(isr);
4098         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4099     } else {
4100         DisplayFatalError(_("Error reading from ICS"), error, 1);
4101     }
4102 }
4103
4104
4105 /* Board style 12 looks like this:
4106
4107    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4108
4109  * The "<12> " is stripped before it gets to this routine.  The two
4110  * trailing 0's (flip state and clock ticking) are later addition, and
4111  * some chess servers may not have them, or may have only the first.
4112  * Additional trailing fields may be added in the future.
4113  */
4114
4115 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4116
4117 #define RELATION_OBSERVING_PLAYED    0
4118 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4119 #define RELATION_PLAYING_MYMOVE      1
4120 #define RELATION_PLAYING_NOTMYMOVE  -1
4121 #define RELATION_EXAMINING           2
4122 #define RELATION_ISOLATED_BOARD     -3
4123 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4124
4125 void
4126 ParseBoard12(string)
4127      char *string;
4128 {
4129     GameMode newGameMode;
4130     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4131     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4132     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4133     char to_play, board_chars[200];
4134     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4135     char black[32], white[32];
4136     Board board;
4137     int prevMove = currentMove;
4138     int ticking = 2;
4139     ChessMove moveType;
4140     int fromX, fromY, toX, toY;
4141     char promoChar;
4142     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4143     char *bookHit = NULL; // [HGM] book
4144     Boolean weird = FALSE, reqFlag = FALSE;
4145
4146     fromX = fromY = toX = toY = -1;
4147
4148     newGame = FALSE;
4149
4150     if (appData.debugMode)
4151       fprintf(debugFP, _("Parsing board: %s\n"), string);
4152
4153     move_str[0] = NULLCHAR;
4154     elapsed_time[0] = NULLCHAR;
4155     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4156         int  i = 0, j;
4157         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4158             if(string[i] == ' ') { ranks++; files = 0; }
4159             else files++;
4160             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4161             i++;
4162         }
4163         for(j = 0; j <i; j++) board_chars[j] = string[j];
4164         board_chars[i] = '\0';
4165         string += i + 1;
4166     }
4167     n = sscanf(string, PATTERN, &to_play, &double_push,
4168                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4169                &gamenum, white, black, &relation, &basetime, &increment,
4170                &white_stren, &black_stren, &white_time, &black_time,
4171                &moveNum, str, elapsed_time, move_str, &ics_flip,
4172                &ticking);
4173
4174     if (n < 21) {
4175         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4176         DisplayError(str, 0);
4177         return;
4178     }
4179
4180     /* Convert the move number to internal form */
4181     moveNum = (moveNum - 1) * 2;
4182     if (to_play == 'B') moveNum++;
4183     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4184       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4185                         0, 1);
4186       return;
4187     }
4188
4189     switch (relation) {
4190       case RELATION_OBSERVING_PLAYED:
4191       case RELATION_OBSERVING_STATIC:
4192         if (gamenum == -1) {
4193             /* Old ICC buglet */
4194             relation = RELATION_OBSERVING_STATIC;
4195         }
4196         newGameMode = IcsObserving;
4197         break;
4198       case RELATION_PLAYING_MYMOVE:
4199       case RELATION_PLAYING_NOTMYMOVE:
4200         newGameMode =
4201           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4202             IcsPlayingWhite : IcsPlayingBlack;
4203         break;
4204       case RELATION_EXAMINING:
4205         newGameMode = IcsExamining;
4206         break;
4207       case RELATION_ISOLATED_BOARD:
4208       default:
4209         /* Just display this board.  If user was doing something else,
4210            we will forget about it until the next board comes. */
4211         newGameMode = IcsIdle;
4212         break;
4213       case RELATION_STARTING_POSITION:
4214         newGameMode = gameMode;
4215         break;
4216     }
4217
4218     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4219          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4220       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4221       char *toSqr;
4222       for (k = 0; k < ranks; k++) {
4223         for (j = 0; j < files; j++)
4224           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4225         if(gameInfo.holdingsWidth > 1) {
4226              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4227              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4228         }
4229       }
4230       CopyBoard(partnerBoard, board);
4231       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4232         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4233         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4235       if(toSqr = strchr(str, '-')) {
4236         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4237         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4239       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4240       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4241       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4243       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4244                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4245       DisplayMessage(partnerStatus, "");
4246         partnerBoardValid = TRUE;
4247       return;
4248     }
4249
4250     /* Modify behavior for initial board display on move listing
4251        of wild games.
4252        */
4253     switch (ics_getting_history) {
4254       case H_FALSE:
4255       case H_REQUESTED:
4256         break;
4257       case H_GOT_REQ_HEADER:
4258       case H_GOT_UNREQ_HEADER:
4259         /* This is the initial position of the current game */
4260         gamenum = ics_gamenum;
4261         moveNum = 0;            /* old ICS bug workaround */
4262         if (to_play == 'B') {
4263           startedFromSetupPosition = TRUE;
4264           blackPlaysFirst = TRUE;
4265           moveNum = 1;
4266           if (forwardMostMove == 0) forwardMostMove = 1;
4267           if (backwardMostMove == 0) backwardMostMove = 1;
4268           if (currentMove == 0) currentMove = 1;
4269         }
4270         newGameMode = gameMode;
4271         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4272         break;
4273       case H_GOT_UNWANTED_HEADER:
4274         /* This is an initial board that we don't want */
4275         return;
4276       case H_GETTING_MOVES:
4277         /* Should not happen */
4278         DisplayError(_("Error gathering move list: extra board"), 0);
4279         ics_getting_history = H_FALSE;
4280         return;
4281     }
4282
4283    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4284                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4285      /* [HGM] We seem to have switched variant unexpectedly
4286       * Try to guess new variant from board size
4287       */
4288           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4289           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4290           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4291           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4292           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4293           if(!weird) newVariant = VariantNormal;
4294           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4295           /* Get a move list just to see the header, which
4296              will tell us whether this is really bug or zh */
4297           if (ics_getting_history == H_FALSE) {
4298             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4299             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4300             SendToICS(str);
4301           }
4302     }
4303
4304     /* Take action if this is the first board of a new game, or of a
4305        different game than is currently being displayed.  */
4306     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4307         relation == RELATION_ISOLATED_BOARD) {
4308
4309         /* Forget the old game and get the history (if any) of the new one */
4310         if (gameMode != BeginningOfGame) {
4311           Reset(TRUE, TRUE);
4312         }
4313         newGame = TRUE;
4314         if (appData.autoRaiseBoard) BoardToTop();
4315         prevMove = -3;
4316         if (gamenum == -1) {
4317             newGameMode = IcsIdle;
4318         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4319                    appData.getMoveList && !reqFlag) {
4320             /* Need to get game history */
4321             ics_getting_history = H_REQUESTED;
4322             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4323             SendToICS(str);
4324         }
4325
4326         /* Initially flip the board to have black on the bottom if playing
4327            black or if the ICS flip flag is set, but let the user change
4328            it with the Flip View button. */
4329         flipView = appData.autoFlipView ?
4330           (newGameMode == IcsPlayingBlack) || ics_flip :
4331           appData.flipView;
4332
4333         /* Done with values from previous mode; copy in new ones */
4334         gameMode = newGameMode;
4335         ModeHighlight();
4336         ics_gamenum = gamenum;
4337         if (gamenum == gs_gamenum) {
4338             int klen = strlen(gs_kind);
4339             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4340             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4341             gameInfo.event = StrSave(str);
4342         } else {
4343             gameInfo.event = StrSave("ICS game");
4344         }
4345         gameInfo.site = StrSave(appData.icsHost);
4346         gameInfo.date = PGNDate();
4347         gameInfo.round = StrSave("-");
4348         gameInfo.white = StrSave(white);
4349         gameInfo.black = StrSave(black);
4350         timeControl = basetime * 60 * 1000;
4351         timeControl_2 = 0;
4352         timeIncrement = increment * 1000;
4353         movesPerSession = 0;
4354         gameInfo.timeControl = TimeControlTagValue();
4355         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4356   if (appData.debugMode) {
4357     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4358     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4359     setbuf(debugFP, NULL);
4360   }
4361
4362         gameInfo.outOfBook = NULL;
4363
4364         /* Do we have the ratings? */
4365         if (strcmp(player1Name, white) == 0 &&
4366             strcmp(player2Name, black) == 0) {
4367             if (appData.debugMode)
4368               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4369                       player1Rating, player2Rating);
4370             gameInfo.whiteRating = player1Rating;
4371             gameInfo.blackRating = player2Rating;
4372         } else if (strcmp(player2Name, white) == 0 &&
4373                    strcmp(player1Name, black) == 0) {
4374             if (appData.debugMode)
4375               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4376                       player2Rating, player1Rating);
4377             gameInfo.whiteRating = player2Rating;
4378             gameInfo.blackRating = player1Rating;
4379         }
4380         player1Name[0] = player2Name[0] = NULLCHAR;
4381
4382         /* Silence shouts if requested */
4383         if (appData.quietPlay &&
4384             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4385             SendToICS(ics_prefix);
4386             SendToICS("set shout 0\n");
4387         }
4388     }
4389
4390     /* Deal with midgame name changes */
4391     if (!newGame) {
4392         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4393             if (gameInfo.white) free(gameInfo.white);
4394             gameInfo.white = StrSave(white);
4395         }
4396         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4397             if (gameInfo.black) free(gameInfo.black);
4398             gameInfo.black = StrSave(black);
4399         }
4400     }
4401
4402     /* Throw away game result if anything actually changes in examine mode */
4403     if (gameMode == IcsExamining && !newGame) {
4404         gameInfo.result = GameUnfinished;
4405         if (gameInfo.resultDetails != NULL) {
4406             free(gameInfo.resultDetails);
4407             gameInfo.resultDetails = NULL;
4408         }
4409     }
4410
4411     /* In pausing && IcsExamining mode, we ignore boards coming
4412        in if they are in a different variation than we are. */
4413     if (pauseExamInvalid) return;
4414     if (pausing && gameMode == IcsExamining) {
4415         if (moveNum <= pauseExamForwardMostMove) {
4416             pauseExamInvalid = TRUE;
4417             forwardMostMove = pauseExamForwardMostMove;
4418             return;
4419         }
4420     }
4421
4422   if (appData.debugMode) {
4423     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4424   }
4425     /* Parse the board */
4426     for (k = 0; k < ranks; k++) {
4427       for (j = 0; j < files; j++)
4428         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429       if(gameInfo.holdingsWidth > 1) {
4430            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432       }
4433     }
4434     CopyBoard(boards[moveNum], board);
4435     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4436     if (moveNum == 0) {
4437         startedFromSetupPosition =
4438           !CompareBoards(board, initialPosition);
4439         if(startedFromSetupPosition)
4440             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4441     }
4442
4443     /* [HGM] Set castling rights. Take the outermost Rooks,
4444        to make it also work for FRC opening positions. Note that board12
4445        is really defective for later FRC positions, as it has no way to
4446        indicate which Rook can castle if they are on the same side of King.
4447        For the initial position we grant rights to the outermost Rooks,
4448        and remember thos rights, and we then copy them on positions
4449        later in an FRC game. This means WB might not recognize castlings with
4450        Rooks that have moved back to their original position as illegal,
4451        but in ICS mode that is not its job anyway.
4452     */
4453     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4454     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4455
4456         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4457             if(board[0][i] == WhiteRook) j = i;
4458         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4460             if(board[0][i] == WhiteRook) j = i;
4461         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4463             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4465         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4466             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4467         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4468
4469         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4470         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4471             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4472         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4473             if(board[BOARD_HEIGHT-1][k] == bKing)
4474                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4475         if(gameInfo.variant == VariantTwoKings) {
4476             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4477             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4478             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4479         }
4480     } else { int r;
4481         r = boards[moveNum][CASTLING][0] = initialRights[0];
4482         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4483         r = boards[moveNum][CASTLING][1] = initialRights[1];
4484         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4485         r = boards[moveNum][CASTLING][3] = initialRights[3];
4486         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4487         r = boards[moveNum][CASTLING][4] = initialRights[4];
4488         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4489         /* wildcastle kludge: always assume King has rights */
4490         r = boards[moveNum][CASTLING][2] = initialRights[2];
4491         r = boards[moveNum][CASTLING][5] = initialRights[5];
4492     }
4493     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4494     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4495
4496
4497     if (ics_getting_history == H_GOT_REQ_HEADER ||
4498         ics_getting_history == H_GOT_UNREQ_HEADER) {
4499         /* This was an initial position from a move list, not
4500            the current position */
4501         return;
4502     }
4503
4504     /* Update currentMove and known move number limits */
4505     newMove = newGame || moveNum > forwardMostMove;
4506
4507     if (newGame) {
4508         forwardMostMove = backwardMostMove = currentMove = moveNum;
4509         if (gameMode == IcsExamining && moveNum == 0) {
4510           /* Workaround for ICS limitation: we are not told the wild
4511              type when starting to examine a game.  But if we ask for
4512              the move list, the move list header will tell us */
4513             ics_getting_history = H_REQUESTED;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516         }
4517     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4518                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4519 #if ZIPPY
4520         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4521         /* [HGM] applied this also to an engine that is silently watching        */
4522         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4523             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4524             gameInfo.variant == currentlyInitializedVariant) {
4525           takeback = forwardMostMove - moveNum;
4526           for (i = 0; i < takeback; i++) {
4527             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4528             SendToProgram("undo\n", &first);
4529           }
4530         }
4531 #endif
4532
4533         forwardMostMove = moveNum;
4534         if (!pausing || currentMove > forwardMostMove)
4535           currentMove = forwardMostMove;
4536     } else {
4537         /* New part of history that is not contiguous with old part */
4538         if (pausing && gameMode == IcsExamining) {
4539             pauseExamInvalid = TRUE;
4540             forwardMostMove = pauseExamForwardMostMove;
4541             return;
4542         }
4543         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4544 #if ZIPPY
4545             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4546                 // [HGM] when we will receive the move list we now request, it will be
4547                 // fed to the engine from the first move on. So if the engine is not
4548                 // in the initial position now, bring it there.
4549                 InitChessProgram(&first, 0);
4550             }
4551 #endif
4552             ics_getting_history = H_REQUESTED;
4553             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4554             SendToICS(str);
4555         }
4556         forwardMostMove = backwardMostMove = currentMove = moveNum;
4557     }
4558
4559     /* Update the clocks */
4560     if (strchr(elapsed_time, '.')) {
4561       /* Time is in ms */
4562       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4563       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4564     } else {
4565       /* Time is in seconds */
4566       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4567       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4568     }
4569
4570
4571 #if ZIPPY
4572     if (appData.zippyPlay && newGame &&
4573         gameMode != IcsObserving && gameMode != IcsIdle &&
4574         gameMode != IcsExamining)
4575       ZippyFirstBoard(moveNum, basetime, increment);
4576 #endif
4577
4578     /* Put the move on the move list, first converting
4579        to canonical algebraic form. */
4580     if (moveNum > 0) {
4581   if (appData.debugMode) {
4582     if (appData.debugMode) { int f = forwardMostMove;
4583         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4584                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4585                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4586     }
4587     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4588     fprintf(debugFP, "moveNum = %d\n", moveNum);
4589     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4590     setbuf(debugFP, NULL);
4591   }
4592         if (moveNum <= backwardMostMove) {
4593             /* We don't know what the board looked like before
4594                this move.  Punt. */
4595           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4596             strcat(parseList[moveNum - 1], " ");
4597             strcat(parseList[moveNum - 1], elapsed_time);
4598             moveList[moveNum - 1][0] = NULLCHAR;
4599         } else if (strcmp(move_str, "none") == 0) {
4600             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4601             /* Again, we don't know what the board looked like;
4602                this is really the start of the game. */
4603             parseList[moveNum - 1][0] = NULLCHAR;
4604             moveList[moveNum - 1][0] = NULLCHAR;
4605             backwardMostMove = moveNum;
4606             startedFromSetupPosition = TRUE;
4607             fromX = fromY = toX = toY = -1;
4608         } else {
4609           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4610           //                 So we parse the long-algebraic move string in stead of the SAN move
4611           int valid; char buf[MSG_SIZ], *prom;
4612
4613           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4614                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4615           // str looks something like "Q/a1-a2"; kill the slash
4616           if(str[1] == '/')
4617             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4618           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4619           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4620                 strcat(buf, prom); // long move lacks promo specification!
4621           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4622                 if(appData.debugMode)
4623                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4624                 safeStrCpy(move_str, buf, MSG_SIZ);
4625           }
4626           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4627                                 &fromX, &fromY, &toX, &toY, &promoChar)
4628                || ParseOneMove(buf, moveNum - 1, &moveType,
4629                                 &fromX, &fromY, &toX, &toY, &promoChar);
4630           // end of long SAN patch
4631           if (valid) {
4632             (void) CoordsToAlgebraic(boards[moveNum - 1],
4633                                      PosFlags(moveNum - 1),
4634                                      fromY, fromX, toY, toX, promoChar,
4635                                      parseList[moveNum-1]);
4636             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4637               case MT_NONE:
4638               case MT_STALEMATE:
4639               default:
4640                 break;
4641               case MT_CHECK:
4642                 if(gameInfo.variant != VariantShogi)
4643                     strcat(parseList[moveNum - 1], "+");
4644                 break;
4645               case MT_CHECKMATE:
4646               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4647                 strcat(parseList[moveNum - 1], "#");
4648                 break;
4649             }
4650             strcat(parseList[moveNum - 1], " ");
4651             strcat(parseList[moveNum - 1], elapsed_time);
4652             /* currentMoveString is set as a side-effect of ParseOneMove */
4653             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4654             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4655             strcat(moveList[moveNum - 1], "\n");
4656
4657             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4658                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4659               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4660                 ChessSquare old, new = boards[moveNum][k][j];
4661                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4662                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4663                   if(old == new) continue;
4664                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4665                   else if(new == WhiteWazir || new == BlackWazir) {
4666                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4667                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4668                       else boards[moveNum][k][j] = old; // preserve type of Gold
4669                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4670                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4671               }
4672           } else {
4673             /* Move from ICS was illegal!?  Punt. */
4674             if (appData.debugMode) {
4675               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4676               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4677             }
4678             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4679             strcat(parseList[moveNum - 1], " ");
4680             strcat(parseList[moveNum - 1], elapsed_time);
4681             moveList[moveNum - 1][0] = NULLCHAR;
4682             fromX = fromY = toX = toY = -1;
4683           }
4684         }
4685   if (appData.debugMode) {
4686     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4687     setbuf(debugFP, NULL);
4688   }
4689
4690 #if ZIPPY
4691         /* Send move to chess program (BEFORE animating it). */
4692         if (appData.zippyPlay && !newGame && newMove &&
4693            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4694
4695             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4696                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4697                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4698                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4699                             move_str);
4700                     DisplayError(str, 0);
4701                 } else {
4702                     if (first.sendTime) {
4703                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4704                     }
4705                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4706                     if (firstMove && !bookHit) {
4707                         firstMove = FALSE;
4708                         if (first.useColors) {
4709                           SendToProgram(gameMode == IcsPlayingWhite ?
4710                                         "white\ngo\n" :
4711                                         "black\ngo\n", &first);
4712                         } else {
4713                           SendToProgram("go\n", &first);
4714                         }
4715                         first.maybeThinking = TRUE;
4716                     }
4717                 }
4718             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4719               if (moveList[moveNum - 1][0] == NULLCHAR) {
4720                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4721                 DisplayError(str, 0);
4722               } else {
4723                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4724                 SendMoveToProgram(moveNum - 1, &first);
4725               }
4726             }
4727         }
4728 #endif
4729     }
4730
4731     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4732         /* If move comes from a remote source, animate it.  If it
4733            isn't remote, it will have already been animated. */
4734         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4735             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4736         }
4737         if (!pausing && appData.highlightLastMove) {
4738             SetHighlights(fromX, fromY, toX, toY);
4739         }
4740     }
4741
4742     /* Start the clocks */
4743     whiteFlag = blackFlag = FALSE;
4744     appData.clockMode = !(basetime == 0 && increment == 0);
4745     if (ticking == 0) {
4746       ics_clock_paused = TRUE;
4747       StopClocks();
4748     } else if (ticking == 1) {
4749       ics_clock_paused = FALSE;
4750     }
4751     if (gameMode == IcsIdle ||
4752         relation == RELATION_OBSERVING_STATIC ||
4753         relation == RELATION_EXAMINING ||
4754         ics_clock_paused)
4755       DisplayBothClocks();
4756     else
4757       StartClocks();
4758
4759     /* Display opponents and material strengths */
4760     if (gameInfo.variant != VariantBughouse &&
4761         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4762         if (tinyLayout || smallLayout) {
4763             if(gameInfo.variant == VariantNormal)
4764               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment);
4767             else
4768               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4769                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4770                     basetime, increment, (int) gameInfo.variant);
4771         } else {
4772             if(gameInfo.variant == VariantNormal)
4773               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment);
4776             else
4777               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4778                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4779                     basetime, increment, VariantName(gameInfo.variant));
4780         }
4781         DisplayTitle(str);
4782   if (appData.debugMode) {
4783     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4784   }
4785     }
4786
4787
4788     /* Display the board */
4789     if (!pausing && !appData.noGUI) {
4790
4791       if (appData.premove)
4792           if (!gotPremove ||
4793              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4794              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4795               ClearPremoveHighlights();
4796
4797       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4798         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4799       DrawPosition(j, boards[currentMove]);
4800
4801       DisplayMove(moveNum - 1);
4802       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4803             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4804               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4805         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4806       }
4807     }
4808
4809     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4810 #if ZIPPY
4811     if(bookHit) { // [HGM] book: simulate book reply
4812         static char bookMove[MSG_SIZ]; // a bit generous?
4813
4814         programStats.nodes = programStats.depth = programStats.time =
4815         programStats.score = programStats.got_only_move = 0;
4816         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4817
4818         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4819         strcat(bookMove, bookHit);
4820         HandleMachineMove(bookMove, &first);
4821     }
4822 #endif
4823 }
4824
4825 void
4826 GetMoveListEvent()
4827 {
4828     char buf[MSG_SIZ];
4829     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4830         ics_getting_history = H_REQUESTED;
4831         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4832         SendToICS(buf);
4833     }
4834 }
4835
4836 void
4837 AnalysisPeriodicEvent(force)
4838      int force;
4839 {
4840     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4841          && !force) || !appData.periodicUpdates)
4842       return;
4843
4844     /* Send . command to Crafty to collect stats */
4845     SendToProgram(".\n", &first);
4846
4847     /* Don't send another until we get a response (this makes
4848        us stop sending to old Crafty's which don't understand
4849        the "." command (sending illegal cmds resets node count & time,
4850        which looks bad)) */
4851     programStats.ok_to_send = 0;
4852 }
4853
4854 void ics_update_width(new_width)
4855         int new_width;
4856 {
4857         ics_printf("set width %d\n", new_width);
4858 }
4859
4860 void
4861 SendMoveToProgram(moveNum, cps)
4862      int moveNum;
4863      ChessProgramState *cps;
4864 {
4865     char buf[MSG_SIZ];
4866
4867     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4868         // null move in variant where engine does not understand it (for analysis purposes)
4869         SendBoard(cps, moveNum + 1); // send position after move in stead.
4870         return;
4871     }
4872     if (cps->useUsermove) {
4873       SendToProgram("usermove ", cps);
4874     }
4875     if (cps->useSAN) {
4876       char *space;
4877       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4878         int len = space - parseList[moveNum];
4879         memcpy(buf, parseList[moveNum], len);
4880         buf[len++] = '\n';
4881         buf[len] = NULLCHAR;
4882       } else {
4883         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4884       }
4885       SendToProgram(buf, cps);
4886     } else {
4887       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4888         AlphaRank(moveList[moveNum], 4);
4889         SendToProgram(moveList[moveNum], cps);
4890         AlphaRank(moveList[moveNum], 4); // and back
4891       } else
4892       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4893        * the engine. It would be nice to have a better way to identify castle
4894        * moves here. */
4895       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4896                                                                          && cps->useOOCastle) {
4897         int fromX = moveList[moveNum][0] - AAA;
4898         int fromY = moveList[moveNum][1] - ONE;
4899         int toX = moveList[moveNum][2] - AAA;
4900         int toY = moveList[moveNum][3] - ONE;
4901         if((boards[moveNum][fromY][fromX] == WhiteKing
4902             && boards[moveNum][toY][toX] == WhiteRook)
4903            || (boards[moveNum][fromY][fromX] == BlackKing
4904                && boards[moveNum][toY][toX] == BlackRook)) {
4905           if(toX > fromX) SendToProgram("O-O\n", cps);
4906           else SendToProgram("O-O-O\n", cps);
4907         }
4908         else SendToProgram(moveList[moveNum], cps);
4909       } else
4910       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4911         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4912           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4913           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4914                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4915         } else
4916           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4917                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4918         SendToProgram(buf, cps);
4919       }
4920       else SendToProgram(moveList[moveNum], cps);
4921       /* End of additions by Tord */
4922     }
4923
4924     /* [HGM] setting up the opening has brought engine in force mode! */
4925     /*       Send 'go' if we are in a mode where machine should play. */
4926     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4927         (gameMode == TwoMachinesPlay   ||
4928 #if ZIPPY
4929          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4930 #endif
4931          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4932         SendToProgram("go\n", cps);
4933   if (appData.debugMode) {
4934     fprintf(debugFP, "(extra)\n");
4935   }
4936     }
4937     setboardSpoiledMachineBlack = 0;
4938 }
4939
4940 void
4941 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4942      ChessMove moveType;
4943      int fromX, fromY, toX, toY;
4944      char promoChar;
4945 {
4946     char user_move[MSG_SIZ];
4947     char suffix[4];
4948
4949     if(gameInfo.variant == VariantSChess && promoChar) {
4950         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4951         if(toX == BOARD_WIDTH>>1) moveType = WhitePromotion; // kludge to do gating at Rook
4952     } else suffix[0] = NULLCHAR;
4953
4954     switch (moveType) {
4955       default:
4956         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4957                 (int)moveType, fromX, fromY, toX, toY);
4958         DisplayError(user_move + strlen("say "), 0);
4959         break;
4960       case WhiteKingSideCastle:
4961       case BlackKingSideCastle:
4962       case WhiteQueenSideCastleWild:
4963       case BlackQueenSideCastleWild:
4964       /* PUSH Fabien */
4965       case WhiteHSideCastleFR:
4966       case BlackHSideCastleFR:
4967       /* POP Fabien */
4968         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4969         break;
4970       case WhiteQueenSideCastle:
4971       case BlackQueenSideCastle:
4972       case WhiteKingSideCastleWild:
4973       case BlackKingSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteASideCastleFR:
4976       case BlackASideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4979         break;
4980       case WhiteNonPromotion:
4981       case BlackNonPromotion:
4982         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4983         break;
4984       case WhitePromotion:
4985       case BlackPromotion:
4986         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4987           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4988                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4989                 PieceToChar(WhiteFerz));
4990         else if(gameInfo.variant == VariantGreat)
4991           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4992                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4993                 PieceToChar(WhiteMan));
4994         else
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 promoChar);
4998         break;
4999       case WhiteDrop:
5000       case BlackDrop:
5001       drop:
5002         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5003                  ToUpper(PieceToChar((ChessSquare) fromX)),
5004                  AAA + toX, ONE + toY);
5005         break;
5006       case IllegalMove:  /* could be a variant we don't quite understand */
5007         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5008       case NormalMove:
5009       case WhiteCapturesEnPassant:
5010       case BlackCapturesEnPassant:
5011         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5012                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5013         break;
5014     }
5015     SendToICS(user_move);
5016     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5017         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5018 }
5019
5020 void
5021 UploadGameEvent()
5022 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5023     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5024     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5025     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5026       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5027       return;
5028     }
5029     if(gameMode != IcsExamining) { // is this ever not the case?
5030         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5031
5032         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5033           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5034         } else { // on FICS we must first go to general examine mode
5035           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5036         }
5037         if(gameInfo.variant != VariantNormal) {
5038             // try figure out wild number, as xboard names are not always valid on ICS
5039             for(i=1; i<=36; i++) {
5040               snprintf(buf, MSG_SIZ, "wild/%d", i);
5041                 if(StringToVariant(buf) == gameInfo.variant) break;
5042             }
5043             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5044             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5045             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5046         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5047         SendToICS(ics_prefix);
5048         SendToICS(buf);
5049         if(startedFromSetupPosition || backwardMostMove != 0) {
5050           fen = PositionToFEN(backwardMostMove, NULL);
5051           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5052             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5053             SendToICS(buf);
5054           } else { // FICS: everything has to set by separate bsetup commands
5055             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5056             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5057             SendToICS(buf);
5058             if(!WhiteOnMove(backwardMostMove)) {
5059                 SendToICS("bsetup tomove black\n");
5060             }
5061             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5062             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5063             SendToICS(buf);
5064             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5065             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5066             SendToICS(buf);
5067             i = boards[backwardMostMove][EP_STATUS];
5068             if(i >= 0) { // set e.p.
5069               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5070                 SendToICS(buf);
5071             }
5072             bsetup++;
5073           }
5074         }
5075       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5076             SendToICS("bsetup done\n"); // switch to normal examining.
5077     }
5078     for(i = backwardMostMove; i<last; i++) {
5079         char buf[20];
5080         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5081         SendToICS(buf);
5082     }
5083     SendToICS(ics_prefix);
5084     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5085 }
5086
5087 void
5088 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5089      int rf, ff, rt, ft;
5090      char promoChar;
5091      char move[7];
5092 {
5093     if (rf == DROP_RANK) {
5094       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5095       sprintf(move, "%c@%c%c\n",
5096                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5097     } else {
5098         if (promoChar == 'x' || promoChar == NULLCHAR) {
5099           sprintf(move, "%c%c%c%c\n",
5100                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5101         } else {
5102             sprintf(move, "%c%c%c%c%c\n",
5103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5104         }
5105     }
5106 }
5107
5108 void
5109 ProcessICSInitScript(f)
5110      FILE *f;
5111 {
5112     char buf[MSG_SIZ];
5113
5114     while (fgets(buf, MSG_SIZ, f)) {
5115         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5116     }
5117
5118     fclose(f);
5119 }
5120
5121
5122 static int lastX, lastY, selectFlag, dragging;
5123
5124 void
5125 Sweep(int step)
5126 {
5127     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5128     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5129     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5130     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5131     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5132     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5133     do {
5134         promoSweep -= step;
5135         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5136         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5137         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5138         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5139         if(!step) step = -1;
5140     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5141             appData.testLegality && (promoSweep == king ||
5142             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5143     ChangeDragPiece(promoSweep);
5144 }
5145
5146 int PromoScroll(int x, int y)
5147 {
5148   int step = 0;
5149
5150   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5151   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5152   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5153   if(!step) return FALSE;
5154   lastX = x; lastY = y;
5155   if((promoSweep < BlackPawn) == flipView) step = -step;
5156   if(step > 0) selectFlag = 1;
5157   if(!selectFlag) Sweep(step);
5158   return FALSE;
5159 }
5160
5161 void
5162 NextPiece(int step)
5163 {
5164     ChessSquare piece = boards[currentMove][toY][toX];
5165     do {
5166         pieceSweep -= step;
5167         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5168         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5169         if(!step) step = -1;
5170     } while(PieceToChar(pieceSweep) == '.');
5171     boards[currentMove][toY][toX] = pieceSweep;
5172     DrawPosition(FALSE, boards[currentMove]);
5173     boards[currentMove][toY][toX] = piece;
5174 }
5175 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5176 void
5177 AlphaRank(char *move, int n)
5178 {
5179 //    char *p = move, c; int x, y;
5180
5181     if (appData.debugMode) {
5182         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5183     }
5184
5185     if(move[1]=='*' &&
5186        move[2]>='0' && move[2]<='9' &&
5187        move[3]>='a' && move[3]<='x'    ) {
5188         move[1] = '@';
5189         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5190         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5191     } else
5192     if(move[0]>='0' && move[0]<='9' &&
5193        move[1]>='a' && move[1]<='x' &&
5194        move[2]>='0' && move[2]<='9' &&
5195        move[3]>='a' && move[3]<='x'    ) {
5196         /* input move, Shogi -> normal */
5197         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5198         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
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[1]=='@' &&
5203        move[3]>='0' && move[3]<='9' &&
5204        move[2]>='a' && move[2]<='x'    ) {
5205         move[1] = '*';
5206         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5208     } else
5209     if(
5210        move[0]>='a' && move[0]<='x' &&
5211        move[3]>='0' && move[3]<='9' &&
5212        move[2]>='a' && move[2]<='x'    ) {
5213          /* output move, normal -> Shogi */
5214         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5215         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5218         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5219     }
5220     if (appData.debugMode) {
5221         fprintf(debugFP, "   out = '%s'\n", move);
5222     }
5223 }
5224
5225 char yy_textstr[8000];
5226
5227 /* Parser for moves from gnuchess, ICS, or user typein box */
5228 Boolean
5229 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5230      char *move;
5231      int moveNum;
5232      ChessMove *moveType;
5233      int *fromX, *fromY, *toX, *toY;
5234      char *promoChar;
5235 {
5236     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5237
5238     switch (*moveType) {
5239       case WhitePromotion:
5240       case BlackPromotion:
5241       case WhiteNonPromotion:
5242       case BlackNonPromotion:
5243       case NormalMove:
5244       case WhiteCapturesEnPassant:
5245       case BlackCapturesEnPassant:
5246       case WhiteKingSideCastle:
5247       case WhiteQueenSideCastle:
5248       case BlackKingSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case WhiteQueenSideCastleWild:
5252       case BlackKingSideCastleWild:
5253       case BlackQueenSideCastleWild:
5254       /* Code added by Tord: */
5255       case WhiteHSideCastleFR:
5256       case WhiteASideCastleFR:
5257       case BlackHSideCastleFR:
5258       case BlackASideCastleFR:
5259       /* End of code added by Tord */
5260       case IllegalMove:         /* bug or odd chess variant */
5261         *fromX = currentMoveString[0] - AAA;
5262         *fromY = currentMoveString[1] - ONE;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = currentMoveString[4];
5266         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5267             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5270     }
5271             *fromX = *fromY = *toX = *toY = 0;
5272             return FALSE;
5273         }
5274         if (appData.testLegality) {
5275           return (*moveType != IllegalMove);
5276         } else {
5277           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5278                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5279         }
5280
5281       case WhiteDrop:
5282       case BlackDrop:
5283         *fromX = *moveType == WhiteDrop ?
5284           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5285           (int) CharToPiece(ToLower(currentMoveString[0]));
5286         *fromY = DROP_RANK;
5287         *toX = currentMoveString[2] - AAA;
5288         *toY = currentMoveString[3] - ONE;
5289         *promoChar = NULLCHAR;
5290         return TRUE;
5291
5292       case AmbiguousMove:
5293       case ImpossibleMove:
5294       case EndOfFile:
5295       case ElapsedTime:
5296       case Comment:
5297       case PGNTag:
5298       case NAG:
5299       case WhiteWins:
5300       case BlackWins:
5301       case GameIsDrawn:
5302       default:
5303     if (appData.debugMode) {
5304         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5305     }
5306         /* bug? */
5307         *fromX = *fromY = *toX = *toY = 0;
5308         *promoChar = NULLCHAR;
5309         return FALSE;
5310     }
5311 }
5312
5313 Boolean pushed = FALSE;
5314 char *lastParseAttempt;
5315
5316 void
5317 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5318 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5319   int fromX, fromY, toX, toY; char promoChar;
5320   ChessMove moveType;
5321   Boolean valid;
5322   int nr = 0;
5323
5324   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5325     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5326     pushed = TRUE;
5327   }
5328   endPV = forwardMostMove;
5329   do {
5330     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5331     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5332     lastParseAttempt = pv;
5333     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5334 if(appData.debugMode){
5335 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);
5336 }
5337     if(!valid && nr == 0 &&
5338        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5339         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5340         // Hande case where played move is different from leading PV move
5341         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5342         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5343         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5344         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5345           endPV += 2; // if position different, keep this
5346           moveList[endPV-1][0] = fromX + AAA;
5347           moveList[endPV-1][1] = fromY + ONE;
5348           moveList[endPV-1][2] = toX + AAA;
5349           moveList[endPV-1][3] = toY + ONE;
5350           parseList[endPV-1][0] = NULLCHAR;
5351           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5352         }
5353       }
5354     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5355     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5356     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5357     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5358         valid++; // allow comments in PV
5359         continue;
5360     }
5361     nr++;
5362     if(endPV+1 > framePtr) break; // no space, truncate
5363     if(!valid) break;
5364     endPV++;
5365     CopyBoard(boards[endPV], boards[endPV-1]);
5366     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5367     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5368     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5369     CoordsToAlgebraic(boards[endPV - 1],
5370                              PosFlags(endPV - 1),
5371                              fromY, fromX, toY, toX, promoChar,
5372                              parseList[endPV - 1]);
5373   } while(valid);
5374   if(atEnd == 2) return; // used hidden, for PV conversion
5375   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5376   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5377   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5378                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5379   DrawPosition(TRUE, boards[currentMove]);
5380 }
5381
5382 int
5383 MultiPV(ChessProgramState *cps)
5384 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5385         int i;
5386         for(i=0; i<cps->nrOptions; i++)
5387             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5388                 return i;
5389         return -1;
5390 }
5391
5392 Boolean
5393 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5394 {
5395         int startPV, multi, lineStart, origIndex = index;
5396         char *p, buf2[MSG_SIZ];
5397
5398         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5399         lastX = x; lastY = y;
5400         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5401         lineStart = startPV = index;
5402         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5403         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5404         index = startPV;
5405         do{ while(buf[index] && buf[index] != '\n') index++;
5406         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5407         buf[index] = 0;
5408         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5409                 int n = first.option[multi].value;
5410                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5411                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5412                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5413                 first.option[multi].value = n;
5414                 *start = *end = 0;
5415                 return FALSE;
5416         }
5417         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5418         *start = startPV; *end = index-1;
5419         return TRUE;
5420 }
5421
5422 char *
5423 PvToSAN(char *pv)
5424 {
5425         static char buf[10*MSG_SIZ];
5426         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5427         *buf = NULLCHAR;
5428         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5429         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5430         for(i = forwardMostMove; i<endPV; i++){
5431             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5432             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5433             k += strlen(buf+k);
5434         }
5435         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5436         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5437         endPV = savedEnd;
5438         return buf;
5439 }
5440
5441 Boolean
5442 LoadPV(int x, int y)
5443 { // called on right mouse click to load PV
5444   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5445   lastX = x; lastY = y;
5446   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5447   return TRUE;
5448 }
5449
5450 void
5451 UnLoadPV()
5452 {
5453   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5454   if(endPV < 0) return;
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV(int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void GetPositionNumber()
5513 {       // sets global variable seed
5514         int i;
5515
5516         seed = appData.defaultFrcPosition;
5517         if(seed < 0) { // randomize based on time for negative FRC position numbers
5518                 for(i=0; i<50; i++) seed += random();
5519                 seed = random() ^ random() >> 8 ^ random() << 8;
5520                 if(seed<0) seed = -seed;
5521         }
5522 }
5523
5524 int put(Board board, int pieceType, int rank, int n, int shade)
5525 // put the piece on the (n-1)-th empty squares of the given shade
5526 {
5527         int i;
5528
5529         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5530                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5531                         board[rank][i] = (ChessSquare) pieceType;
5532                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5533                         squaresLeft[ANY]--;
5534                         piecesLeft[pieceType]--;
5535                         return i;
5536                 }
5537         }
5538         return -1;
5539 }
5540
5541
5542 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5543 // calculate where the next piece goes, (any empty square), and put it there
5544 {
5545         int i;
5546
5547         i = seed % squaresLeft[shade];
5548         nrOfShuffles *= squaresLeft[shade];
5549         seed /= squaresLeft[shade];
5550         put(board, pieceType, rank, i, shade);
5551 }
5552
5553 void AddTwoPieces(Board board, int pieceType, int rank)
5554 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5555 {
5556         int i, n=squaresLeft[ANY], j=n-1, k;
5557
5558         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5559         i = seed % k;  // pick one
5560         nrOfShuffles *= k;
5561         seed /= k;
5562         while(i >= j) i -= j--;
5563         j = n - 1 - j; i += j;
5564         put(board, pieceType, rank, j, ANY);
5565         put(board, pieceType, rank, i, ANY);
5566 }
5567
5568 void SetUpShuffle(Board board, int number)
5569 {
5570         int i, p, first=1;
5571
5572         GetPositionNumber(); nrOfShuffles = 1;
5573
5574         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5575         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5576         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5577
5578         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5579
5580         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5581             p = (int) board[0][i];
5582             if(p < (int) BlackPawn) piecesLeft[p] ++;
5583             board[0][i] = EmptySquare;
5584         }
5585
5586         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5587             // shuffles restricted to allow normal castling put KRR first
5588             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5589                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5590             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5591                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5593                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5595                 put(board, WhiteRook, 0, 0, ANY);
5596             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5597         }
5598
5599         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5600             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5601             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5602                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5603                 while(piecesLeft[p] >= 2) {
5604                     AddOnePiece(board, p, 0, LITE);
5605                     AddOnePiece(board, p, 0, DARK);
5606                 }
5607                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5608             }
5609
5610         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5611             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5612             // but we leave King and Rooks for last, to possibly obey FRC restriction
5613             if(p == (int)WhiteRook) continue;
5614             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5615             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5616         }
5617
5618         // now everything is placed, except perhaps King (Unicorn) and Rooks
5619
5620         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5621             // Last King gets castling rights
5622             while(piecesLeft[(int)WhiteUnicorn]) {
5623                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5624                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5625             }
5626
5627             while(piecesLeft[(int)WhiteKing]) {
5628                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632
5633         } else {
5634             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5635             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5636         }
5637
5638         // Only Rooks can be left; simply place them all
5639         while(piecesLeft[(int)WhiteRook]) {
5640                 i = put(board, WhiteRook, 0, 0, ANY);
5641                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5642                         if(first) {
5643                                 first=0;
5644                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5645                         }
5646                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5647                 }
5648         }
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5650             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5651         }
5652
5653         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5654 }
5655
5656 int SetCharTable( char *table, const char * map )
5657 /* [HGM] moved here from winboard.c because of its general usefulness */
5658 /*       Basically a safe strcpy that uses the last character as King */
5659 {
5660     int result = FALSE; int NrPieces;
5661
5662     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5663                     && NrPieces >= 12 && !(NrPieces&1)) {
5664         int i; /* [HGM] Accept even length from 12 to 34 */
5665
5666         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5667         for( i=0; i<NrPieces/2-1; i++ ) {
5668             table[i] = map[i];
5669             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5670         }
5671         table[(int) WhiteKing]  = map[NrPieces/2-1];
5672         table[(int) BlackKing]  = map[NrPieces-1];
5673
5674         result = TRUE;
5675     }
5676
5677     return result;
5678 }
5679
5680 void Prelude(Board board)
5681 {       // [HGM] superchess: random selection of exo-pieces
5682         int i, j, k; ChessSquare p;
5683         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5684
5685         GetPositionNumber(); // use FRC position number
5686
5687         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5688             SetCharTable(pieceToChar, appData.pieceToCharTable);
5689             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5690                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5691         }
5692
5693         j = seed%4;                 seed /= 4;
5694         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5695         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5696         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5697         j = seed%3 + (seed%3 >= j); seed /= 3;
5698         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5699         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5700         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5701         j = seed%3;                 seed /= 3;
5702         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5703         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5704         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5705         j = seed%2 + (seed%2 >= j); seed /= 2;
5706         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5709         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5710         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5711         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5712         put(board, exoPieces[0],    0, 0, ANY);
5713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5714 }
5715
5716 void
5717 InitPosition(redraw)
5718      int redraw;
5719 {
5720     ChessSquare (* pieces)[BOARD_FILES];
5721     int i, j, pawnRow, overrule,
5722     oldx = gameInfo.boardWidth,
5723     oldy = gameInfo.boardHeight,
5724     oldh = gameInfo.holdingsWidth;
5725     static int oldv;
5726
5727     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5728
5729     /* [AS] Initialize pv info list [HGM] and game status */
5730     {
5731         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5732             pvInfoList[i].depth = 0;
5733             boards[i][EP_STATUS] = EP_NONE;
5734             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5735         }
5736
5737         initialRulePlies = 0; /* 50-move counter start */
5738
5739         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5740         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5741     }
5742
5743
5744     /* [HGM] logic here is completely changed. In stead of full positions */
5745     /* the initialized data only consist of the two backranks. The switch */
5746     /* selects which one we will use, which is than copied to the Board   */
5747     /* initialPosition, which for the rest is initialized by Pawns and    */
5748     /* empty squares. This initial position is then copied to boards[0],  */
5749     /* possibly after shuffling, so that it remains available.            */
5750
5751     gameInfo.holdingsWidth = 0; /* default board sizes */
5752     gameInfo.boardWidth    = 8;
5753     gameInfo.boardHeight   = 8;
5754     gameInfo.holdingsSize  = 0;
5755     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5756     for(i=0; i<BOARD_FILES-2; i++)
5757       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5758     initialPosition[EP_STATUS] = EP_NONE;
5759     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5760     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5761          SetCharTable(pieceNickName, appData.pieceNickNames);
5762     else SetCharTable(pieceNickName, "............");
5763     pieces = FIDEArray;
5764
5765     switch (gameInfo.variant) {
5766     case VariantFischeRandom:
5767       shuffleOpenings = TRUE;
5768     default:
5769       break;
5770     case VariantShatranj:
5771       pieces = ShatranjArray;
5772       nrCastlingRights = 0;
5773       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5774       break;
5775     case VariantMakruk:
5776       pieces = makrukArray;
5777       nrCastlingRights = 0;
5778       startedFromSetupPosition = TRUE;
5779       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5780       break;
5781     case VariantTwoKings:
5782       pieces = twoKingsArray;
5783       break;
5784     case VariantGrand:
5785       pieces = GrandArray;
5786       nrCastlingRights = 0;
5787       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5788       gameInfo.boardWidth = 10;
5789       gameInfo.boardHeight = 10;
5790       gameInfo.holdingsSize = 7;
5791       break;
5792     case VariantCapaRandom:
5793       shuffleOpenings = TRUE;
5794     case VariantCapablanca:
5795       pieces = CapablancaArray;
5796       gameInfo.boardWidth = 10;
5797       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5798       break;
5799     case VariantGothic:
5800       pieces = GothicArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5803       break;
5804     case VariantSChess:
5805       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5806       gameInfo.holdingsSize = 7;
5807       break;
5808     case VariantJanus:
5809       pieces = JanusArray;
5810       gameInfo.boardWidth = 10;
5811       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5812       nrCastlingRights = 6;
5813         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5814         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5815         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5816         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5817         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5818         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5819       break;
5820     case VariantFalcon:
5821       pieces = FalconArray;
5822       gameInfo.boardWidth = 10;
5823       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5824       break;
5825     case VariantXiangqi:
5826       pieces = XiangqiArray;
5827       gameInfo.boardWidth  = 9;
5828       gameInfo.boardHeight = 10;
5829       nrCastlingRights = 0;
5830       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5831       break;
5832     case VariantShogi:
5833       pieces = ShogiArray;
5834       gameInfo.boardWidth  = 9;
5835       gameInfo.boardHeight = 9;
5836       gameInfo.holdingsSize = 7;
5837       nrCastlingRights = 0;
5838       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5839       break;
5840     case VariantCourier:
5841       pieces = CourierArray;
5842       gameInfo.boardWidth  = 12;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5845       break;
5846     case VariantKnightmate:
5847       pieces = KnightmateArray;
5848       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5849       break;
5850     case VariantSpartan:
5851       pieces = SpartanArray;
5852       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5853       break;
5854     case VariantFairy:
5855       pieces = fairyArray;
5856       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5857       break;
5858     case VariantGreat:
5859       pieces = GreatArray;
5860       gameInfo.boardWidth = 10;
5861       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5862       gameInfo.holdingsSize = 8;
5863       break;
5864     case VariantSuper:
5865       pieces = FIDEArray;
5866       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5867       gameInfo.holdingsSize = 8;
5868       startedFromSetupPosition = TRUE;
5869       break;
5870     case VariantCrazyhouse:
5871     case VariantBughouse:
5872       pieces = FIDEArray;
5873       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5874       gameInfo.holdingsSize = 5;
5875       break;
5876     case VariantWildCastle:
5877       pieces = FIDEArray;
5878       /* !!?shuffle with kings guaranteed to be on d or e file */
5879       shuffleOpenings = 1;
5880       break;
5881     case VariantNoCastle:
5882       pieces = FIDEArray;
5883       nrCastlingRights = 0;
5884       /* !!?unconstrained back-rank shuffle */
5885       shuffleOpenings = 1;
5886       break;
5887     }
5888
5889     overrule = 0;
5890     if(appData.NrFiles >= 0) {
5891         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5892         gameInfo.boardWidth = appData.NrFiles;
5893     }
5894     if(appData.NrRanks >= 0) {
5895         gameInfo.boardHeight = appData.NrRanks;
5896     }
5897     if(appData.holdingsSize >= 0) {
5898         i = appData.holdingsSize;
5899         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5900         gameInfo.holdingsSize = i;
5901     }
5902     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5903     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5904         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5905
5906     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5907     if(pawnRow < 1) pawnRow = 1;
5908     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5909
5910     /* User pieceToChar list overrules defaults */
5911     if(appData.pieceToCharTable != NULL)
5912         SetCharTable(pieceToChar, appData.pieceToCharTable);
5913
5914     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5915
5916         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5917             s = (ChessSquare) 0; /* account holding counts in guard band */
5918         for( i=0; i<BOARD_HEIGHT; i++ )
5919             initialPosition[i][j] = s;
5920
5921         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5922         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5923         initialPosition[pawnRow][j] = WhitePawn;
5924         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5925         if(gameInfo.variant == VariantXiangqi) {
5926             if(j&1) {
5927                 initialPosition[pawnRow][j] =
5928                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5929                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5930                    initialPosition[2][j] = WhiteCannon;
5931                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5932                 }
5933             }
5934         }
5935         if(gameInfo.variant == VariantGrand) {
5936             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5937                initialPosition[0][j] = WhiteRook;
5938                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5939             }
5940         }
5941         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5942     }
5943     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5944
5945             j=BOARD_LEFT+1;
5946             initialPosition[1][j] = WhiteBishop;
5947             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5948             j=BOARD_RGHT-2;
5949             initialPosition[1][j] = WhiteRook;
5950             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5951     }
5952
5953     if( nrCastlingRights == -1) {
5954         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5955         /*       This sets default castling rights from none to normal corners   */
5956         /* Variants with other castling rights must set them themselves above    */
5957         nrCastlingRights = 6;
5958
5959         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5960         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5961         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5962         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5963         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5964         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5965      }
5966
5967      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5968      if(gameInfo.variant == VariantGreat) { // promotion commoners
5969         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5970         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5971         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5972         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5973      }
5974      if( gameInfo.variant == VariantSChess ) {
5975       initialPosition[1][0] = BlackMarshall;
5976       initialPosition[2][0] = BlackAngel;
5977       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5978       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5979       initialPosition[1][1] = initialPosition[2][1] = 
5980       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5981      }
5982   if (appData.debugMode) {
5983     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5984   }
5985     if(shuffleOpenings) {
5986         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5987         startedFromSetupPosition = TRUE;
5988     }
5989     if(startedFromPositionFile) {
5990       /* [HGM] loadPos: use PositionFile for every new game */
5991       CopyBoard(initialPosition, filePosition);
5992       for(i=0; i<nrCastlingRights; i++)
5993           initialRights[i] = filePosition[CASTLING][i];
5994       startedFromSetupPosition = TRUE;
5995     }
5996
5997     CopyBoard(boards[0], initialPosition);
5998
5999     if(oldx != gameInfo.boardWidth ||
6000        oldy != gameInfo.boardHeight ||
6001        oldv != gameInfo.variant ||
6002        oldh != gameInfo.holdingsWidth
6003                                          )
6004             InitDrawingSizes(-2 ,0);
6005
6006     oldv = gameInfo.variant;
6007     if (redraw)
6008       DrawPosition(TRUE, boards[currentMove]);
6009 }
6010
6011 void
6012 SendBoard(cps, moveNum)
6013      ChessProgramState *cps;
6014      int moveNum;
6015 {
6016     char message[MSG_SIZ];
6017
6018     if (cps->useSetboard) {
6019       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6020       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6021       SendToProgram(message, cps);
6022       free(fen);
6023
6024     } else {
6025       ChessSquare *bp;
6026       int i, j;
6027       /* Kludge to set black to move, avoiding the troublesome and now
6028        * deprecated "black" command.
6029        */
6030       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6031         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6032
6033       SendToProgram("edit\n", cps);
6034       SendToProgram("#\n", cps);
6035       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6036         bp = &boards[moveNum][i][BOARD_LEFT];
6037         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6038           if ((int) *bp < (int) BlackPawn) {
6039             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6040                     AAA + j, ONE + i);
6041             if(message[0] == '+' || message[0] == '~') {
6042               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6043                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6044                         AAA + j, ONE + i);
6045             }
6046             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6047                 message[1] = BOARD_RGHT   - 1 - j + '1';
6048                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6049             }
6050             SendToProgram(message, cps);
6051           }
6052         }
6053       }
6054
6055       SendToProgram("c\n", cps);
6056       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6057         bp = &boards[moveNum][i][BOARD_LEFT];
6058         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6059           if (((int) *bp != (int) EmptySquare)
6060               && ((int) *bp >= (int) BlackPawn)) {
6061             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6062                     AAA + j, ONE + i);
6063             if(message[0] == '+' || message[0] == '~') {
6064               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6065                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6066                         AAA + j, ONE + i);
6067             }
6068             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6069                 message[1] = BOARD_RGHT   - 1 - j + '1';
6070                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6071             }
6072             SendToProgram(message, cps);
6073           }
6074         }
6075       }
6076
6077       SendToProgram(".\n", cps);
6078     }
6079     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6080 }
6081
6082 ChessSquare
6083 DefaultPromoChoice(int white)
6084 {
6085     ChessSquare result;
6086     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6087         result = WhiteFerz; // no choice
6088     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6089         result= WhiteKing; // in Suicide Q is the last thing we want
6090     else if(gameInfo.variant == VariantSpartan)
6091         result = white ? WhiteQueen : WhiteAngel;
6092     else result = WhiteQueen;
6093     if(!white) result = WHITE_TO_BLACK result;
6094     return result;
6095 }
6096
6097 static int autoQueen; // [HGM] oneclick
6098
6099 int
6100 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6101 {
6102     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6103     /* [HGM] add Shogi promotions */
6104     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6105     ChessSquare piece;
6106     ChessMove moveType;
6107     Boolean premove;
6108
6109     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6110     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6111
6112     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6113       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6114         return FALSE;
6115
6116     piece = boards[currentMove][fromY][fromX];
6117     if(gameInfo.variant == VariantShogi) {
6118         promotionZoneSize = BOARD_HEIGHT/3;
6119         highestPromotingPiece = (int)WhiteFerz;
6120     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6121         promotionZoneSize = 3;
6122     }
6123
6124     // Treat Lance as Pawn when it is not representing Amazon
6125     if(gameInfo.variant != VariantSuper) {
6126         if(piece == WhiteLance) piece = WhitePawn; else
6127         if(piece == BlackLance) piece = BlackPawn;
6128     }
6129
6130     // next weed out all moves that do not touch the promotion zone at all
6131     if((int)piece >= BlackPawn) {
6132         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6133              return FALSE;
6134         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6135     } else {
6136         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6137            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6138     }
6139
6140     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6141
6142     // weed out mandatory Shogi promotions
6143     if(gameInfo.variant == VariantShogi) {
6144         if(piece >= BlackPawn) {
6145             if(toY == 0 && piece == BlackPawn ||
6146                toY == 0 && piece == BlackQueen ||
6147                toY <= 1 && piece == BlackKnight) {
6148                 *promoChoice = '+';
6149                 return FALSE;
6150             }
6151         } else {
6152             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6153                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6154                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6155                 *promoChoice = '+';
6156                 return FALSE;
6157             }
6158         }
6159     }
6160
6161     // weed out obviously illegal Pawn moves
6162     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6163         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6164         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6165         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6166         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6167         // note we are not allowed to test for valid (non-)capture, due to premove
6168     }
6169
6170     // we either have a choice what to promote to, or (in Shogi) whether to promote
6171     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6172         *promoChoice = PieceToChar(BlackFerz);  // no choice
6173         return FALSE;
6174     }
6175     // no sense asking what we must promote to if it is going to explode...
6176     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6177         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6178         return FALSE;
6179     }
6180     // give caller the default choice even if we will not make it
6181     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6182     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6183     if(        sweepSelect && gameInfo.variant != VariantGreat
6184                            && gameInfo.variant != VariantGrand
6185                            && gameInfo.variant != VariantSuper) return FALSE;
6186     if(autoQueen) return FALSE; // predetermined
6187
6188     // suppress promotion popup on illegal moves that are not premoves
6189     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6190               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6191     if(appData.testLegality && !premove) {
6192         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6193                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6194         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6195             return FALSE;
6196     }
6197
6198     return TRUE;
6199 }
6200
6201 int
6202 InPalace(row, column)
6203      int row, column;
6204 {   /* [HGM] for Xiangqi */
6205     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6206          column < (BOARD_WIDTH + 4)/2 &&
6207          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6208     return FALSE;
6209 }
6210
6211 int
6212 PieceForSquare (x, y)
6213      int x;
6214      int y;
6215 {
6216   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6217      return -1;
6218   else
6219      return boards[currentMove][y][x];
6220 }
6221
6222 int
6223 OKToStartUserMove(x, y)
6224      int x, y;
6225 {
6226     ChessSquare from_piece;
6227     int white_piece;
6228
6229     if (matchMode) return FALSE;
6230     if (gameMode == EditPosition) return TRUE;
6231
6232     if (x >= 0 && y >= 0)
6233       from_piece = boards[currentMove][y][x];
6234     else
6235       from_piece = EmptySquare;
6236
6237     if (from_piece == EmptySquare) return FALSE;
6238
6239     white_piece = (int)from_piece >= (int)WhitePawn &&
6240       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6241
6242     switch (gameMode) {
6243       case AnalyzeFile:
6244       case TwoMachinesPlay:
6245       case EndOfGame:
6246         return FALSE;
6247
6248       case IcsObserving:
6249       case IcsIdle:
6250         return FALSE;
6251
6252       case MachinePlaysWhite:
6253       case IcsPlayingBlack:
6254         if (appData.zippyPlay) return FALSE;
6255         if (white_piece) {
6256             DisplayMoveError(_("You are playing Black"));
6257             return FALSE;
6258         }
6259         break;
6260
6261       case MachinePlaysBlack:
6262       case IcsPlayingWhite:
6263         if (appData.zippyPlay) return FALSE;
6264         if (!white_piece) {
6265             DisplayMoveError(_("You are playing White"));
6266             return FALSE;
6267         }
6268         break;
6269
6270       case PlayFromGameFile:
6271             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6272       case EditGame:
6273         if (!white_piece && WhiteOnMove(currentMove)) {
6274             DisplayMoveError(_("It is White's turn"));
6275             return FALSE;
6276         }
6277         if (white_piece && !WhiteOnMove(currentMove)) {
6278             DisplayMoveError(_("It is Black's turn"));
6279             return FALSE;
6280         }
6281         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6282             /* Editing correspondence game history */
6283             /* Could disallow this or prompt for confirmation */
6284             cmailOldMove = -1;
6285         }
6286         break;
6287
6288       case BeginningOfGame:
6289         if (appData.icsActive) return FALSE;
6290         if (!appData.noChessProgram) {
6291             if (!white_piece) {
6292                 DisplayMoveError(_("You are playing White"));
6293                 return FALSE;
6294             }
6295         }
6296         break;
6297
6298       case Training:
6299         if (!white_piece && WhiteOnMove(currentMove)) {
6300             DisplayMoveError(_("It is White's turn"));
6301             return FALSE;
6302         }
6303         if (white_piece && !WhiteOnMove(currentMove)) {
6304             DisplayMoveError(_("It is Black's turn"));
6305             return FALSE;
6306         }
6307         break;
6308
6309       default:
6310       case IcsExamining:
6311         break;
6312     }
6313     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6314         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6315         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6316         && gameMode != AnalyzeFile && gameMode != Training) {
6317         DisplayMoveError(_("Displayed position is not current"));
6318         return FALSE;
6319     }
6320     return TRUE;
6321 }
6322
6323 Boolean
6324 OnlyMove(int *x, int *y, Boolean captures) {
6325     DisambiguateClosure cl;
6326     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6327     switch(gameMode) {
6328       case MachinePlaysBlack:
6329       case IcsPlayingWhite:
6330       case BeginningOfGame:
6331         if(!WhiteOnMove(currentMove)) return FALSE;
6332         break;
6333       case MachinePlaysWhite:
6334       case IcsPlayingBlack:
6335         if(WhiteOnMove(currentMove)) return FALSE;
6336         break;
6337       case EditGame:
6338         break;
6339       default:
6340         return FALSE;
6341     }
6342     cl.pieceIn = EmptySquare;
6343     cl.rfIn = *y;
6344     cl.ffIn = *x;
6345     cl.rtIn = -1;
6346     cl.ftIn = -1;
6347     cl.promoCharIn = NULLCHAR;
6348     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6349     if( cl.kind == NormalMove ||
6350         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6351         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6352         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6353       fromX = cl.ff;
6354       fromY = cl.rf;
6355       *x = cl.ft;
6356       *y = cl.rt;
6357       return TRUE;
6358     }
6359     if(cl.kind != ImpossibleMove) return FALSE;
6360     cl.pieceIn = EmptySquare;
6361     cl.rfIn = -1;
6362     cl.ffIn = -1;
6363     cl.rtIn = *y;
6364     cl.ftIn = *x;
6365     cl.promoCharIn = NULLCHAR;
6366     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6367     if( cl.kind == NormalMove ||
6368         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6369         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6370         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6371       fromX = cl.ff;
6372       fromY = cl.rf;
6373       *x = cl.ft;
6374       *y = cl.rt;
6375       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6376       return TRUE;
6377     }
6378     return FALSE;
6379 }
6380
6381 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6382 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6383 int lastLoadGameUseList = FALSE;
6384 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6385 ChessMove lastLoadGameStart = EndOfFile;
6386
6387 void
6388 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6389      int fromX, fromY, toX, toY;
6390      int promoChar;
6391 {
6392     ChessMove moveType;
6393     ChessSquare pdown, pup;
6394
6395     /* Check if the user is playing in turn.  This is complicated because we
6396        let the user "pick up" a piece before it is his turn.  So the piece he
6397        tried to pick up may have been captured by the time he puts it down!
6398        Therefore we use the color the user is supposed to be playing in this
6399        test, not the color of the piece that is currently on the starting
6400        square---except in EditGame mode, where the user is playing both
6401        sides; fortunately there the capture race can't happen.  (It can
6402        now happen in IcsExamining mode, but that's just too bad.  The user
6403        will get a somewhat confusing message in that case.)
6404        */
6405
6406     switch (gameMode) {
6407       case AnalyzeFile:
6408       case TwoMachinesPlay:
6409       case EndOfGame:
6410       case IcsObserving:
6411       case IcsIdle:
6412         /* We switched into a game mode where moves are not accepted,
6413            perhaps while the mouse button was down. */
6414         return;
6415
6416       case MachinePlaysWhite:
6417         /* User is moving for Black */
6418         if (WhiteOnMove(currentMove)) {
6419             DisplayMoveError(_("It is White's turn"));
6420             return;
6421         }
6422         break;
6423
6424       case MachinePlaysBlack:
6425         /* User is moving for White */
6426         if (!WhiteOnMove(currentMove)) {
6427             DisplayMoveError(_("It is Black's turn"));
6428             return;
6429         }
6430         break;
6431
6432       case PlayFromGameFile:
6433             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6434       case EditGame:
6435       case IcsExamining:
6436       case BeginningOfGame:
6437       case AnalyzeMode:
6438       case Training:
6439         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6440         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6441             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6442             /* User is moving for Black */
6443             if (WhiteOnMove(currentMove)) {
6444                 DisplayMoveError(_("It is White's turn"));
6445                 return;
6446             }
6447         } else {
6448             /* User is moving for White */
6449             if (!WhiteOnMove(currentMove)) {
6450                 DisplayMoveError(_("It is Black's turn"));
6451                 return;
6452             }
6453         }
6454         break;
6455
6456       case IcsPlayingBlack:
6457         /* User is moving for Black */
6458         if (WhiteOnMove(currentMove)) {
6459             if (!appData.premove) {
6460                 DisplayMoveError(_("It is White's turn"));
6461             } else if (toX >= 0 && toY >= 0) {
6462                 premoveToX = toX;
6463                 premoveToY = toY;
6464                 premoveFromX = fromX;
6465                 premoveFromY = fromY;
6466                 premovePromoChar = promoChar;
6467                 gotPremove = 1;
6468                 if (appData.debugMode)
6469                     fprintf(debugFP, "Got premove: fromX %d,"
6470                             "fromY %d, toX %d, toY %d\n",
6471                             fromX, fromY, toX, toY);
6472             }
6473             return;
6474         }
6475         break;
6476
6477       case IcsPlayingWhite:
6478         /* User is moving for White */
6479         if (!WhiteOnMove(currentMove)) {
6480             if (!appData.premove) {
6481                 DisplayMoveError(_("It is Black's turn"));
6482             } else if (toX >= 0 && toY >= 0) {
6483                 premoveToX = toX;
6484                 premoveToY = toY;
6485                 premoveFromX = fromX;
6486                 premoveFromY = fromY;
6487                 premovePromoChar = promoChar;
6488                 gotPremove = 1;
6489                 if (appData.debugMode)
6490                     fprintf(debugFP, "Got premove: fromX %d,"
6491                             "fromY %d, toX %d, toY %d\n",
6492                             fromX, fromY, toX, toY);
6493             }
6494             return;
6495         }
6496         break;
6497
6498       default:
6499         break;
6500
6501       case EditPosition:
6502         /* EditPosition, empty square, or different color piece;
6503            click-click move is possible */
6504         if (toX == -2 || toY == -2) {
6505             boards[0][fromY][fromX] = EmptySquare;
6506             DrawPosition(FALSE, boards[currentMove]);
6507             return;
6508         } else if (toX >= 0 && toY >= 0) {
6509             boards[0][toY][toX] = boards[0][fromY][fromX];
6510             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6511                 if(boards[0][fromY][0] != EmptySquare) {
6512                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6513                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6514                 }
6515             } else
6516             if(fromX == BOARD_RGHT+1) {
6517                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6518                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6519                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6520                 }
6521             } else
6522             boards[0][fromY][fromX] = EmptySquare;
6523             DrawPosition(FALSE, boards[currentMove]);
6524             return;
6525         }
6526         return;
6527     }
6528
6529     if(toX < 0 || toY < 0) return;
6530     pdown = boards[currentMove][fromY][fromX];
6531     pup = boards[currentMove][toY][toX];
6532
6533     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6534     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6535          if( pup != EmptySquare ) return;
6536          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6537            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6538                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6539            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6540            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6541            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6542            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6543          fromY = DROP_RANK;
6544     }
6545
6546     /* [HGM] always test for legality, to get promotion info */
6547     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6548                                          fromY, fromX, toY, toX, promoChar);
6549
6550     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6551
6552     /* [HGM] but possibly ignore an IllegalMove result */
6553     if (appData.testLegality) {
6554         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6555             DisplayMoveError(_("Illegal move"));
6556             return;
6557         }
6558     }
6559
6560     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6561 }
6562
6563 /* Common tail of UserMoveEvent and DropMenuEvent */
6564 int
6565 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6566      ChessMove moveType;
6567      int fromX, fromY, toX, toY;
6568      /*char*/int promoChar;
6569 {
6570     char *bookHit = 0;
6571
6572     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6573         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6574         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6575         if(WhiteOnMove(currentMove)) {
6576             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6577         } else {
6578             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6579         }
6580     }
6581
6582     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6583        move type in caller when we know the move is a legal promotion */
6584     if(moveType == NormalMove && promoChar)
6585         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6586
6587     /* [HGM] <popupFix> The following if has been moved here from
6588        UserMoveEvent(). Because it seemed to belong here (why not allow
6589        piece drops in training games?), and because it can only be
6590        performed after it is known to what we promote. */
6591     if (gameMode == Training) {
6592       /* compare the move played on the board to the next move in the
6593        * game. If they match, display the move and the opponent's response.
6594        * If they don't match, display an error message.
6595        */
6596       int saveAnimate;
6597       Board testBoard;
6598       CopyBoard(testBoard, boards[currentMove]);
6599       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6600
6601       if (CompareBoards(testBoard, boards[currentMove+1])) {
6602         ForwardInner(currentMove+1);
6603
6604         /* Autoplay the opponent's response.
6605          * if appData.animate was TRUE when Training mode was entered,
6606          * the response will be animated.
6607          */
6608         saveAnimate = appData.animate;
6609         appData.animate = animateTraining;
6610         ForwardInner(currentMove+1);
6611         appData.animate = saveAnimate;
6612
6613         /* check for the end of the game */
6614         if (currentMove >= forwardMostMove) {
6615           gameMode = PlayFromGameFile;
6616           ModeHighlight();
6617           SetTrainingModeOff();
6618           DisplayInformation(_("End of game"));
6619         }
6620       } else {
6621         DisplayError(_("Incorrect move"), 0);
6622       }
6623       return 1;
6624     }
6625
6626   /* Ok, now we know that the move is good, so we can kill
6627      the previous line in Analysis Mode */
6628   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6629                                 && currentMove < forwardMostMove) {
6630     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6631     else forwardMostMove = currentMove;
6632   }
6633
6634   /* If we need the chess program but it's dead, restart it */
6635   ResurrectChessProgram();
6636
6637   /* A user move restarts a paused game*/
6638   if (pausing)
6639     PauseEvent();
6640
6641   thinkOutput[0] = NULLCHAR;
6642
6643   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6644
6645   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6646     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6647     return 1;
6648   }
6649
6650   if (gameMode == BeginningOfGame) {
6651     if (appData.noChessProgram) {
6652       gameMode = EditGame;
6653       SetGameInfo();
6654     } else {
6655       char buf[MSG_SIZ];
6656       gameMode = MachinePlaysBlack;
6657       StartClocks();
6658       SetGameInfo();
6659       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6660       DisplayTitle(buf);
6661       if (first.sendName) {
6662         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6663         SendToProgram(buf, &first);
6664       }
6665       StartClocks();
6666     }
6667     ModeHighlight();
6668   }
6669
6670   /* Relay move to ICS or chess engine */
6671   if (appData.icsActive) {
6672     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6673         gameMode == IcsExamining) {
6674       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6675         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6676         SendToICS("draw ");
6677         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6678       }
6679       // also send plain move, in case ICS does not understand atomic claims
6680       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6681       ics_user_moved = 1;
6682     }
6683   } else {
6684     if (first.sendTime && (gameMode == BeginningOfGame ||
6685                            gameMode == MachinePlaysWhite ||
6686                            gameMode == MachinePlaysBlack)) {
6687       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6688     }
6689     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6690          // [HGM] book: if program might be playing, let it use book
6691         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6692         first.maybeThinking = TRUE;
6693     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6694         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6695         SendBoard(&first, currentMove+1);
6696     } else SendMoveToProgram(forwardMostMove-1, &first);
6697     if (currentMove == cmailOldMove + 1) {
6698       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6699     }
6700   }
6701
6702   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6703
6704   switch (gameMode) {
6705   case EditGame:
6706     if(appData.testLegality)
6707     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6708     case MT_NONE:
6709     case MT_CHECK:
6710       break;
6711     case MT_CHECKMATE:
6712     case MT_STAINMATE:
6713       if (WhiteOnMove(currentMove)) {
6714         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6715       } else {
6716         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6717       }
6718       break;
6719     case MT_STALEMATE:
6720       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6721       break;
6722     }
6723     break;
6724
6725   case MachinePlaysBlack:
6726   case MachinePlaysWhite:
6727     /* disable certain menu options while machine is thinking */
6728     SetMachineThinkingEnables();
6729     break;
6730
6731   default:
6732     break;
6733   }
6734
6735   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6736   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6737
6738   if(bookHit) { // [HGM] book: simulate book reply
6739         static char bookMove[MSG_SIZ]; // a bit generous?
6740
6741         programStats.nodes = programStats.depth = programStats.time =
6742         programStats.score = programStats.got_only_move = 0;
6743         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6744
6745         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6746         strcat(bookMove, bookHit);
6747         HandleMachineMove(bookMove, &first);
6748   }
6749   return 1;
6750 }
6751
6752 void
6753 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6754      Board board;
6755      int flags;
6756      ChessMove kind;
6757      int rf, ff, rt, ft;
6758      VOIDSTAR closure;
6759 {
6760     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6761     Markers *m = (Markers *) closure;
6762     if(rf == fromY && ff == fromX)
6763         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6764                          || kind == WhiteCapturesEnPassant
6765                          || kind == BlackCapturesEnPassant);
6766     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6767 }
6768
6769 void
6770 MarkTargetSquares(int clear)
6771 {
6772   int x, y;
6773   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6774      !appData.testLegality || gameMode == EditPosition) return;
6775   if(clear) {
6776     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6777   } else {
6778     int capt = 0;
6779     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6780     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6781       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6782       if(capt)
6783       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6784     }
6785   }
6786   DrawPosition(TRUE, NULL);
6787 }
6788
6789 int
6790 Explode(Board board, int fromX, int fromY, int toX, int toY)
6791 {
6792     if(gameInfo.variant == VariantAtomic &&
6793        (board[toY][toX] != EmptySquare ||                     // capture?
6794         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6795                          board[fromY][fromX] == BlackPawn   )
6796       )) {
6797         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6798         return TRUE;
6799     }
6800     return FALSE;
6801 }
6802
6803 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6804
6805 int CanPromote(ChessSquare piece, int y)
6806 {
6807         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6808         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6809         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6810            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6811            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6812                                                   gameInfo.variant == VariantMakruk) return FALSE;
6813         return (piece == BlackPawn && y == 1 ||
6814                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6815                 piece == BlackLance && y == 1 ||
6816                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6817 }
6818
6819 void LeftClick(ClickType clickType, int xPix, int yPix)
6820 {
6821     int x, y;
6822     Boolean saveAnimate;
6823     static int second = 0, promotionChoice = 0, clearFlag = 0;
6824     char promoChoice = NULLCHAR;
6825     ChessSquare piece;
6826
6827     if(appData.seekGraph && appData.icsActive && loggedOn &&
6828         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6829         SeekGraphClick(clickType, xPix, yPix, 0);
6830         return;
6831     }
6832
6833     if (clickType == Press) ErrorPopDown();
6834
6835     x = EventToSquare(xPix, BOARD_WIDTH);
6836     y = EventToSquare(yPix, BOARD_HEIGHT);
6837     if (!flipView && y >= 0) {
6838         y = BOARD_HEIGHT - 1 - y;
6839     }
6840     if (flipView && x >= 0) {
6841         x = BOARD_WIDTH - 1 - x;
6842     }
6843
6844     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6845         defaultPromoChoice = promoSweep;
6846         promoSweep = EmptySquare;   // terminate sweep
6847         promoDefaultAltered = TRUE;
6848         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6849     }
6850
6851     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6852         if(clickType == Release) return; // ignore upclick of click-click destination
6853         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6854         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6855         if(gameInfo.holdingsWidth &&
6856                 (WhiteOnMove(currentMove)
6857                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6858                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6859             // click in right holdings, for determining promotion piece
6860             ChessSquare p = boards[currentMove][y][x];
6861             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6862             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6863             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6864                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6865                 fromX = fromY = -1;
6866                 return;
6867             }
6868         }
6869         DrawPosition(FALSE, boards[currentMove]);
6870         return;
6871     }
6872
6873     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6874     if(clickType == Press
6875             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6876               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6877               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6878         return;
6879
6880     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6881         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6882
6883     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6884         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6885                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6886         defaultPromoChoice = DefaultPromoChoice(side);
6887     }
6888
6889     autoQueen = appData.alwaysPromoteToQueen;
6890
6891     if (fromX == -1) {
6892       int originalY = y;
6893       gatingPiece = EmptySquare;
6894       if (clickType != Press) {
6895         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6896             DragPieceEnd(xPix, yPix); dragging = 0;
6897             DrawPosition(FALSE, NULL);
6898         }
6899         return;
6900       }
6901       fromX = x; fromY = y; toX = toY = -1;
6902       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6903          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6904          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6905             /* First square */
6906             if (OKToStartUserMove(fromX, fromY)) {
6907                 second = 0;
6908                 MarkTargetSquares(0);
6909                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6910                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6911                     promoSweep = defaultPromoChoice;
6912                     selectFlag = 0; lastX = xPix; lastY = yPix;
6913                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6914                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6915                 }
6916                 if (appData.highlightDragging) {
6917                     SetHighlights(fromX, fromY, -1, -1);
6918                 }
6919             } else fromX = fromY = -1;
6920             return;
6921         }
6922     }
6923
6924     /* fromX != -1 */
6925     if (clickType == Press && gameMode != EditPosition) {
6926         ChessSquare fromP;
6927         ChessSquare toP;
6928         int frc;
6929
6930         // ignore off-board to clicks
6931         if(y < 0 || x < 0) return;
6932
6933         /* Check if clicking again on the same color piece */
6934         fromP = boards[currentMove][fromY][fromX];
6935         toP = boards[currentMove][y][x];
6936         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6937         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6938              WhitePawn <= toP && toP <= WhiteKing &&
6939              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6940              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6941             (BlackPawn <= fromP && fromP <= BlackKing &&
6942              BlackPawn <= toP && toP <= BlackKing &&
6943              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6944              !(fromP == BlackKing && toP == BlackRook && frc))) {
6945             /* Clicked again on same color piece -- changed his mind */
6946             second = (x == fromX && y == fromY);
6947             promoDefaultAltered = FALSE;
6948             MarkTargetSquares(1);
6949            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6950             if (appData.highlightDragging) {
6951                 SetHighlights(x, y, -1, -1);
6952             } else {
6953                 ClearHighlights();
6954             }
6955             if (OKToStartUserMove(x, y)) {
6956                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6957                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6958                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6959                  gatingPiece = boards[currentMove][fromY][fromX];
6960                 else gatingPiece = EmptySquare;
6961                 fromX = x;
6962                 fromY = y; dragging = 1;
6963                 MarkTargetSquares(0);
6964                 DragPieceBegin(xPix, yPix, FALSE);
6965                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6966                     promoSweep = defaultPromoChoice;
6967                     selectFlag = 0; lastX = xPix; lastY = yPix;
6968                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6969                 }
6970             }
6971            }
6972            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6973            second = FALSE; 
6974         }
6975         // ignore clicks on holdings
6976         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6977     }
6978
6979     if (clickType == Release && x == fromX && y == fromY) {
6980         DragPieceEnd(xPix, yPix); dragging = 0;
6981         if(clearFlag) {
6982             // a deferred attempt to click-click move an empty square on top of a piece
6983             boards[currentMove][y][x] = EmptySquare;
6984             ClearHighlights();
6985             DrawPosition(FALSE, boards[currentMove]);
6986             fromX = fromY = -1; clearFlag = 0;
6987             return;
6988         }
6989         if (appData.animateDragging) {
6990             /* Undo animation damage if any */
6991             DrawPosition(FALSE, NULL);
6992         }
6993         if (second) {
6994             /* Second up/down in same square; just abort move */
6995             second = 0;
6996             fromX = fromY = -1;
6997             gatingPiece = EmptySquare;
6998             ClearHighlights();
6999             gotPremove = 0;
7000             ClearPremoveHighlights();
7001         } else {
7002             /* First upclick in same square; start click-click mode */
7003             SetHighlights(x, y, -1, -1);
7004         }
7005         return;
7006     }
7007
7008     clearFlag = 0;
7009
7010     /* we now have a different from- and (possibly off-board) to-square */
7011     /* Completed move */
7012     toX = x;
7013     toY = y;
7014     saveAnimate = appData.animate;
7015     MarkTargetSquares(1);
7016     if (clickType == Press) {
7017         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7018             // must be Edit Position mode with empty-square selected
7019             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7020             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7021             return;
7022         }
7023         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7024             ChessSquare piece = boards[currentMove][fromY][fromX];
7025             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7026             promoSweep = defaultPromoChoice;
7027             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7028             selectFlag = 0; lastX = xPix; lastY = yPix;
7029             Sweep(0); // Pawn that is going to promote: preview promotion piece
7030             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7031             DrawPosition(FALSE, boards[currentMove]);
7032             return;
7033         }
7034         /* Finish clickclick move */
7035         if (appData.animate || appData.highlightLastMove) {
7036             SetHighlights(fromX, fromY, toX, toY);
7037         } else {
7038             ClearHighlights();
7039         }
7040     } else {
7041         /* Finish drag move */
7042         if (appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047         DragPieceEnd(xPix, yPix); dragging = 0;
7048         /* Don't animate move and drag both */
7049         appData.animate = FALSE;
7050     }
7051
7052     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7053     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7054         ChessSquare piece = boards[currentMove][fromY][fromX];
7055         if(gameMode == EditPosition && piece != EmptySquare &&
7056            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7057             int n;
7058
7059             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7060                 n = PieceToNumber(piece - (int)BlackPawn);
7061                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7064             } else
7065             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7066                 n = PieceToNumber(piece);
7067                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7068                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7069                 boards[currentMove][n][BOARD_WIDTH-2]++;
7070             }
7071             boards[currentMove][fromY][fromX] = EmptySquare;
7072         }
7073         ClearHighlights();
7074         fromX = fromY = -1;
7075         DrawPosition(TRUE, boards[currentMove]);
7076         return;
7077     }
7078
7079     // off-board moves should not be highlighted
7080     if(x < 0 || y < 0) ClearHighlights();
7081
7082     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7083
7084     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7085         SetHighlights(fromX, fromY, toX, toY);
7086         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7087             // [HGM] super: promotion to captured piece selected from holdings
7088             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7089             promotionChoice = TRUE;
7090             // kludge follows to temporarily execute move on display, without promoting yet
7091             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7092             boards[currentMove][toY][toX] = p;
7093             DrawPosition(FALSE, boards[currentMove]);
7094             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7095             boards[currentMove][toY][toX] = q;
7096             DisplayMessage("Click in holdings to choose piece", "");
7097             return;
7098         }
7099         PromotionPopUp();
7100     } else {
7101         int oldMove = currentMove;
7102         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7103         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7104         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7105         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7106            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7107             DrawPosition(TRUE, boards[currentMove]);
7108         fromX = fromY = -1;
7109     }
7110     appData.animate = saveAnimate;
7111     if (appData.animate || appData.animateDragging) {
7112         /* Undo animation damage if needed */
7113         DrawPosition(FALSE, NULL);
7114     }
7115 }
7116
7117 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7118 {   // front-end-free part taken out of PieceMenuPopup
7119     int whichMenu; int xSqr, ySqr;
7120
7121     if(seekGraphUp) { // [HGM] seekgraph
7122         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7123         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7124         return -2;
7125     }
7126
7127     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7128          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7129         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7130         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7131         if(action == Press)   {
7132             originalFlip = flipView;
7133             flipView = !flipView; // temporarily flip board to see game from partners perspective
7134             DrawPosition(TRUE, partnerBoard);
7135             DisplayMessage(partnerStatus, "");
7136             partnerUp = TRUE;
7137         } else if(action == Release) {
7138             flipView = originalFlip;
7139             DrawPosition(TRUE, boards[currentMove]);
7140             partnerUp = FALSE;
7141         }
7142         return -2;
7143     }
7144
7145     xSqr = EventToSquare(x, BOARD_WIDTH);
7146     ySqr = EventToSquare(y, BOARD_HEIGHT);
7147     if (action == Release) {
7148         if(pieceSweep != EmptySquare) {
7149             EditPositionMenuEvent(pieceSweep, toX, toY);
7150             pieceSweep = EmptySquare;
7151         } else UnLoadPV(); // [HGM] pv
7152     }
7153     if (action != Press) return -2; // return code to be ignored
7154     switch (gameMode) {
7155       case IcsExamining:
7156         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7157       case EditPosition:
7158         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7159         if (xSqr < 0 || ySqr < 0) return -1;
7160         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7161         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7162         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7163         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7164         NextPiece(0);
7165         return 2; // grab
7166       case IcsObserving:
7167         if(!appData.icsEngineAnalyze) return -1;
7168       case IcsPlayingWhite:
7169       case IcsPlayingBlack:
7170         if(!appData.zippyPlay) goto noZip;
7171       case AnalyzeMode:
7172       case AnalyzeFile:
7173       case MachinePlaysWhite:
7174       case MachinePlaysBlack:
7175       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7176         if (!appData.dropMenu) {
7177           LoadPV(x, y);
7178           return 2; // flag front-end to grab mouse events
7179         }
7180         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7181            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7182       case EditGame:
7183       noZip:
7184         if (xSqr < 0 || ySqr < 0) return -1;
7185         if (!appData.dropMenu || appData.testLegality &&
7186             gameInfo.variant != VariantBughouse &&
7187             gameInfo.variant != VariantCrazyhouse) return -1;
7188         whichMenu = 1; // drop menu
7189         break;
7190       default:
7191         return -1;
7192     }
7193
7194     if (((*fromX = xSqr) < 0) ||
7195         ((*fromY = ySqr) < 0)) {
7196         *fromX = *fromY = -1;
7197         return -1;
7198     }
7199     if (flipView)
7200       *fromX = BOARD_WIDTH - 1 - *fromX;
7201     else
7202       *fromY = BOARD_HEIGHT - 1 - *fromY;
7203
7204     return whichMenu;
7205 }
7206
7207 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7208 {
7209 //    char * hint = lastHint;
7210     FrontEndProgramStats stats;
7211
7212     stats.which = cps == &first ? 0 : 1;
7213     stats.depth = cpstats->depth;
7214     stats.nodes = cpstats->nodes;
7215     stats.score = cpstats->score;
7216     stats.time = cpstats->time;
7217     stats.pv = cpstats->movelist;
7218     stats.hint = lastHint;
7219     stats.an_move_index = 0;
7220     stats.an_move_count = 0;
7221
7222     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7223         stats.hint = cpstats->move_name;
7224         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7225         stats.an_move_count = cpstats->nr_moves;
7226     }
7227
7228     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
7229
7230     SetProgramStats( &stats );
7231 }
7232
7233 void
7234 ClearEngineOutputPane(int which)
7235 {
7236     static FrontEndProgramStats dummyStats;
7237     dummyStats.which = which;
7238     dummyStats.pv = "#";
7239     SetProgramStats( &dummyStats );
7240 }
7241
7242 #define MAXPLAYERS 500
7243
7244 char *
7245 TourneyStandings(int display)
7246 {
7247     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7248     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7249     char result, *p, *names[MAXPLAYERS];
7250
7251     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7252         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7253     names[0] = p = strdup(appData.participants);
7254     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7255
7256     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7257
7258     while(result = appData.results[nr]) {
7259         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7260         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7261         wScore = bScore = 0;
7262         switch(result) {
7263           case '+': wScore = 2; break;
7264           case '-': bScore = 2; break;
7265           case '=': wScore = bScore = 1; break;
7266           case ' ':
7267           case '*': return strdup("busy"); // tourney not finished
7268         }
7269         score[w] += wScore;
7270         score[b] += bScore;
7271         games[w]++;
7272         games[b]++;
7273         nr++;
7274     }
7275     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7276     for(w=0; w<nPlayers; w++) {
7277         bScore = -1;
7278         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7279         ranking[w] = b; points[w] = bScore; score[b] = -2;
7280     }
7281     p = malloc(nPlayers*34+1);
7282     for(w=0; w<nPlayers && w<display; w++)
7283         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7284     free(names[0]);
7285     return p;
7286 }
7287
7288 void
7289 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7290 {       // count all piece types
7291         int p, f, r;
7292         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7293         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7294         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7295                 p = board[r][f];
7296                 pCnt[p]++;
7297                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7298                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7299                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7300                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7301                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7302                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7303         }
7304 }
7305
7306 int
7307 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7308 {
7309         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7310         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7311
7312         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7313         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7314         if(myPawns == 2 && nMine == 3) // KPP
7315             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7316         if(myPawns == 1 && nMine == 2) // KP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7318         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7320         if(myPawns) return FALSE;
7321         if(pCnt[WhiteRook+side])
7322             return pCnt[BlackRook-side] ||
7323                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7324                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7325                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7326         if(pCnt[WhiteCannon+side]) {
7327             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7328             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7329         }
7330         if(pCnt[WhiteKnight+side])
7331             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7332         return FALSE;
7333 }
7334
7335 int
7336 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7337 {
7338         VariantClass v = gameInfo.variant;
7339
7340         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7341         if(v == VariantShatranj) return TRUE; // always winnable through baring
7342         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7343         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7344
7345         if(v == VariantXiangqi) {
7346                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7347
7348                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7349                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7350                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7351                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7352                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7353                 if(stale) // we have at least one last-rank P plus perhaps C
7354                     return majors // KPKX
7355                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7356                 else // KCA*E*
7357                     return pCnt[WhiteFerz+side] // KCAK
7358                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7359                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7360                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7361
7362         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7363                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7364
7365                 if(nMine == 1) return FALSE; // bare King
7366                 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
7367                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7368                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7369                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7370                 if(pCnt[WhiteKnight+side])
7371                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7372                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7373                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7374                 if(nBishops)
7375                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7376                 if(pCnt[WhiteAlfil+side])
7377                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7378                 if(pCnt[WhiteWazir+side])
7379                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7380         }
7381
7382         return TRUE;
7383 }
7384
7385 int
7386 CompareWithRights(Board b1, Board b2)
7387 {
7388     int rights = 0;
7389     if(!CompareBoards(b1, b2)) return FALSE;
7390     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7391     /* compare castling rights */
7392     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7393            rights++; /* King lost rights, while rook still had them */
7394     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7395         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7396            rights++; /* but at least one rook lost them */
7397     }
7398     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7399            rights++;
7400     if( b1[CASTLING][5] != NoRights ) {
7401         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7402            rights++;
7403     }
7404     return rights == 0;
7405 }
7406
7407 int
7408 Adjudicate(ChessProgramState *cps)
7409 {       // [HGM] some adjudications useful with buggy engines
7410         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7411         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7412         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7413         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7414         int k, count = 0; static int bare = 1;
7415         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7416         Boolean canAdjudicate = !appData.icsActive;
7417
7418         // most tests only when we understand the game, i.e. legality-checking on
7419             if( appData.testLegality )
7420             {   /* [HGM] Some more adjudications for obstinate engines */
7421                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7422                 static int moveCount = 6;
7423                 ChessMove result;
7424                 char *reason = NULL;
7425
7426                 /* Count what is on board. */
7427                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7428
7429                 /* Some material-based adjudications that have to be made before stalemate test */
7430                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7431                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7432                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7433                      if(canAdjudicate && appData.checkMates) {
7434                          if(engineOpponent)
7435                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7436                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7437                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7438                          return 1;
7439                      }
7440                 }
7441
7442                 /* Bare King in Shatranj (loses) or Losers (wins) */
7443                 if( nrW == 1 || nrB == 1) {
7444                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7445                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7446                      if(canAdjudicate && appData.checkMates) {
7447                          if(engineOpponent)
7448                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7449                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7450                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7451                          return 1;
7452                      }
7453                   } else
7454                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7455                   {    /* bare King */
7456                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7457                         if(canAdjudicate && appData.checkMates) {
7458                             /* but only adjudicate if adjudication enabled */
7459                             if(engineOpponent)
7460                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7461                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7462                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7463                             return 1;
7464                         }
7465                   }
7466                 } else bare = 1;
7467
7468
7469             // don't wait for engine to announce game end if we can judge ourselves
7470             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7471               case MT_CHECK:
7472                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7473                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7474                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7475                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7476                             checkCnt++;
7477                         if(checkCnt >= 2) {
7478                             reason = "Xboard adjudication: 3rd check";
7479                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7480                             break;
7481                         }
7482                     }
7483                 }
7484               case MT_NONE:
7485               default:
7486                 break;
7487               case MT_STALEMATE:
7488               case MT_STAINMATE:
7489                 reason = "Xboard adjudication: Stalemate";
7490                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7491                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7492                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7493                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7494                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7495                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7496                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7497                                                                         EP_CHECKMATE : EP_WINS);
7498                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7499                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7500                 }
7501                 break;
7502               case MT_CHECKMATE:
7503                 reason = "Xboard adjudication: Checkmate";
7504                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7505                 break;
7506             }
7507
7508                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7509                     case EP_STALEMATE:
7510                         result = GameIsDrawn; break;
7511                     case EP_CHECKMATE:
7512                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7513                     case EP_WINS:
7514                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7515                     default:
7516                         result = EndOfFile;
7517                 }
7518                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7519                     if(engineOpponent)
7520                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7521                     GameEnds( result, reason, GE_XBOARD );
7522                     return 1;
7523                 }
7524
7525                 /* Next absolutely insufficient mating material. */
7526                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7527                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7528                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7529
7530                      /* always flag draws, for judging claims */
7531                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7532
7533                      if(canAdjudicate && appData.materialDraws) {
7534                          /* but only adjudicate them if adjudication enabled */
7535                          if(engineOpponent) {
7536                            SendToProgram("force\n", engineOpponent); // suppress reply
7537                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7538                          }
7539                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7540                          return 1;
7541                      }
7542                 }
7543
7544                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7545                 if(gameInfo.variant == VariantXiangqi ?
7546                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7547                  : nrW + nrB == 4 &&
7548                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7549                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7550                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7551                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7552                    ) ) {
7553                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7554                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7555                           if(engineOpponent) {
7556                             SendToProgram("force\n", engineOpponent); // suppress reply
7557                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7558                           }
7559                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7560                           return 1;
7561                      }
7562                 } else moveCount = 6;
7563             }
7564         if (appData.debugMode) { int i;
7565             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7566                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7567                     appData.drawRepeats);
7568             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7569               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7570
7571         }
7572
7573         // Repetition draws and 50-move rule can be applied independently of legality testing
7574
7575                 /* Check for rep-draws */
7576                 count = 0;
7577                 for(k = forwardMostMove-2;
7578                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7579                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7580                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7581                     k-=2)
7582                 {   int rights=0;
7583                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7584                         /* compare castling rights */
7585                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7586                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7587                                 rights++; /* King lost rights, while rook still had them */
7588                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7589                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7590                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7591                                    rights++; /* but at least one rook lost them */
7592                         }
7593                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7594                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7595                                 rights++;
7596                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7597                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7598                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7599                                    rights++;
7600                         }
7601                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7602                             && appData.drawRepeats > 1) {
7603                              /* adjudicate after user-specified nr of repeats */
7604                              int result = GameIsDrawn;
7605                              char *details = "XBoard adjudication: repetition draw";
7606                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7607                                 // [HGM] xiangqi: check for forbidden perpetuals
7608                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7609                                 for(m=forwardMostMove; m>k; m-=2) {
7610                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7611                                         ourPerpetual = 0; // the current mover did not always check
7612                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7613                                         hisPerpetual = 0; // the opponent did not always check
7614                                 }
7615                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7616                                                                         ourPerpetual, hisPerpetual);
7617                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7618                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7619                                     details = "Xboard adjudication: perpetual checking";
7620                                 } else
7621                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7622                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7623                                 } else
7624                                 // Now check for perpetual chases
7625                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7626                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7627                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7628                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7629                                         static char resdet[MSG_SIZ];
7630                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7631                                         details = resdet;
7632                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7633                                     } else
7634                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7635                                         break; // Abort repetition-checking loop.
7636                                 }
7637                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7638                              }
7639                              if(engineOpponent) {
7640                                SendToProgram("force\n", engineOpponent); // suppress reply
7641                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7642                              }
7643                              GameEnds( result, details, GE_XBOARD );
7644                              return 1;
7645                         }
7646                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7647                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7648                     }
7649                 }
7650
7651                 /* Now we test for 50-move draws. Determine ply count */
7652                 count = forwardMostMove;
7653                 /* look for last irreversble move */
7654                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7655                     count--;
7656                 /* if we hit starting position, add initial plies */
7657                 if( count == backwardMostMove )
7658                     count -= initialRulePlies;
7659                 count = forwardMostMove - count;
7660                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7661                         // adjust reversible move counter for checks in Xiangqi
7662                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7663                         if(i < backwardMostMove) i = backwardMostMove;
7664                         while(i <= forwardMostMove) {
7665                                 lastCheck = inCheck; // check evasion does not count
7666                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7667                                 if(inCheck || lastCheck) count--; // check does not count
7668                                 i++;
7669                         }
7670                 }
7671                 if( count >= 100)
7672                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7673                          /* this is used to judge if draw claims are legal */
7674                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7675                          if(engineOpponent) {
7676                            SendToProgram("force\n", engineOpponent); // suppress reply
7677                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                          }
7679                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7680                          return 1;
7681                 }
7682
7683                 /* if draw offer is pending, treat it as a draw claim
7684                  * when draw condition present, to allow engines a way to
7685                  * claim draws before making their move to avoid a race
7686                  * condition occurring after their move
7687                  */
7688                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7689                          char *p = NULL;
7690                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7691                              p = "Draw claim: 50-move rule";
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7693                              p = "Draw claim: 3-fold repetition";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7695                              p = "Draw claim: insufficient mating material";
7696                          if( p != NULL && canAdjudicate) {
7697                              if(engineOpponent) {
7698                                SendToProgram("force\n", engineOpponent); // suppress reply
7699                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                              }
7701                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7702                              return 1;
7703                          }
7704                 }
7705
7706                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7707                     if(engineOpponent) {
7708                       SendToProgram("force\n", engineOpponent); // suppress reply
7709                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7710                     }
7711                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7712                     return 1;
7713                 }
7714         return 0;
7715 }
7716
7717 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7718 {   // [HGM] book: this routine intercepts moves to simulate book replies
7719     char *bookHit = NULL;
7720
7721     //first determine if the incoming move brings opponent into his book
7722     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7723         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7724     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7725     if(bookHit != NULL && !cps->bookSuspend) {
7726         // make sure opponent is not going to reply after receiving move to book position
7727         SendToProgram("force\n", cps);
7728         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7729     }
7730     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7731     // now arrange restart after book miss
7732     if(bookHit) {
7733         // after a book hit we never send 'go', and the code after the call to this routine
7734         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7735         char buf[MSG_SIZ], *move = bookHit;
7736         if(cps->useSAN) {
7737             int fromX, fromY, toX, toY;
7738             char promoChar;
7739             ChessMove moveType;
7740             move = buf + 30;
7741             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7742                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7743                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7744                                     PosFlags(forwardMostMove),
7745                                     fromY, fromX, toY, toX, promoChar, move);
7746             } else {
7747                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7748                 bookHit = NULL;
7749             }
7750         }
7751         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7752         SendToProgram(buf, cps);
7753         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7754     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7755         SendToProgram("go\n", cps);
7756         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7757     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7758         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7759             SendToProgram("go\n", cps);
7760         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7761     }
7762     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7763 }
7764
7765 char *savedMessage;
7766 ChessProgramState *savedState;
7767 void DeferredBookMove(void)
7768 {
7769         if(savedState->lastPing != savedState->lastPong)
7770                     ScheduleDelayedEvent(DeferredBookMove, 10);
7771         else
7772         HandleMachineMove(savedMessage, savedState);
7773 }
7774
7775 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7776
7777 void
7778 HandleMachineMove(message, cps)
7779      char *message;
7780      ChessProgramState *cps;
7781 {
7782     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7783     char realname[MSG_SIZ];
7784     int fromX, fromY, toX, toY;
7785     ChessMove moveType;
7786     char promoChar;
7787     char *p, *pv=buf1;
7788     int machineWhite;
7789     char *bookHit;
7790
7791     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7792         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7793         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7794             DisplayError(_("Invalid pairing from pairing engine"), 0);
7795             return;
7796         }
7797         pairingReceived = 1;
7798         NextMatchGame();
7799         return; // Skim the pairing messages here.
7800     }
7801
7802     cps->userError = 0;
7803
7804 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7805     /*
7806      * Kludge to ignore BEL characters
7807      */
7808     while (*message == '\007') message++;
7809
7810     /*
7811      * [HGM] engine debug message: ignore lines starting with '#' character
7812      */
7813     if(cps->debug && *message == '#') return;
7814
7815     /*
7816      * Look for book output
7817      */
7818     if (cps == &first && bookRequested) {
7819         if (message[0] == '\t' || message[0] == ' ') {
7820             /* Part of the book output is here; append it */
7821             strcat(bookOutput, message);
7822             strcat(bookOutput, "  \n");
7823             return;
7824         } else if (bookOutput[0] != NULLCHAR) {
7825             /* All of book output has arrived; display it */
7826             char *p = bookOutput;
7827             while (*p != NULLCHAR) {
7828                 if (*p == '\t') *p = ' ';
7829                 p++;
7830             }
7831             DisplayInformation(bookOutput);
7832             bookRequested = FALSE;
7833             /* Fall through to parse the current output */
7834         }
7835     }
7836
7837     /*
7838      * Look for machine move.
7839      */
7840     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7841         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7842     {
7843         /* This method is only useful on engines that support ping */
7844         if (cps->lastPing != cps->lastPong) {
7845           if (gameMode == BeginningOfGame) {
7846             /* Extra move from before last new; ignore */
7847             if (appData.debugMode) {
7848                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7849             }
7850           } else {
7851             if (appData.debugMode) {
7852                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7853                         cps->which, gameMode);
7854             }
7855
7856             SendToProgram("undo\n", cps);
7857           }
7858           return;
7859         }
7860
7861         switch (gameMode) {
7862           case BeginningOfGame:
7863             /* Extra move from before last reset; ignore */
7864             if (appData.debugMode) {
7865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7866             }
7867             return;
7868
7869           case EndOfGame:
7870           case IcsIdle:
7871           default:
7872             /* Extra move after we tried to stop.  The mode test is
7873                not a reliable way of detecting this problem, but it's
7874                the best we can do on engines that don't support ping.
7875             */
7876             if (appData.debugMode) {
7877                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7878                         cps->which, gameMode);
7879             }
7880             SendToProgram("undo\n", cps);
7881             return;
7882
7883           case MachinePlaysWhite:
7884           case IcsPlayingWhite:
7885             machineWhite = TRUE;
7886             break;
7887
7888           case MachinePlaysBlack:
7889           case IcsPlayingBlack:
7890             machineWhite = FALSE;
7891             break;
7892
7893           case TwoMachinesPlay:
7894             machineWhite = (cps->twoMachinesColor[0] == 'w');
7895             break;
7896         }
7897         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7898             if (appData.debugMode) {
7899                 fprintf(debugFP,
7900                         "Ignoring move out of turn by %s, gameMode %d"
7901                         ", forwardMost %d\n",
7902                         cps->which, gameMode, forwardMostMove);
7903             }
7904             return;
7905         }
7906
7907     if (appData.debugMode) { int f = forwardMostMove;
7908         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7909                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7910                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7911     }
7912         if(cps->alphaRank) AlphaRank(machineMove, 4);
7913         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7914                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7915             /* Machine move could not be parsed; ignore it. */
7916           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7917                     machineMove, _(cps->which));
7918             DisplayError(buf1, 0);
7919             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7920                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7921             if (gameMode == TwoMachinesPlay) {
7922               GameEnds(machineWhite ? BlackWins : WhiteWins,
7923                        buf1, GE_XBOARD);
7924             }
7925             return;
7926         }
7927
7928         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7929         /* So we have to redo legality test with true e.p. status here,  */
7930         /* to make sure an illegal e.p. capture does not slip through,   */
7931         /* to cause a forfeit on a justified illegal-move complaint      */
7932         /* of the opponent.                                              */
7933         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7934            ChessMove moveType;
7935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7936                              fromY, fromX, toY, toX, promoChar);
7937             if (appData.debugMode) {
7938                 int i;
7939                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7940                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7941                 fprintf(debugFP, "castling rights\n");
7942             }
7943             if(moveType == IllegalMove) {
7944               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7946                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7947                            buf1, GE_XBOARD);
7948                 return;
7949            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7950            /* [HGM] Kludge to handle engines that send FRC-style castling
7951               when they shouldn't (like TSCP-Gothic) */
7952            switch(moveType) {
7953              case WhiteASideCastleFR:
7954              case BlackASideCastleFR:
7955                toX+=2;
7956                currentMoveString[2]++;
7957                break;
7958              case WhiteHSideCastleFR:
7959              case BlackHSideCastleFR:
7960                toX--;
7961                currentMoveString[2]--;
7962                break;
7963              default: ; // nothing to do, but suppresses warning of pedantic compilers
7964            }
7965         }
7966         hintRequested = FALSE;
7967         lastHint[0] = NULLCHAR;
7968         bookRequested = FALSE;
7969         /* Program may be pondering now */
7970         cps->maybeThinking = TRUE;
7971         if (cps->sendTime == 2) cps->sendTime = 1;
7972         if (cps->offeredDraw) cps->offeredDraw--;
7973
7974         /* [AS] Save move info*/
7975         pvInfoList[ forwardMostMove ].score = programStats.score;
7976         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7977         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7978
7979         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7980
7981         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7982         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7983             int count = 0;
7984
7985             while( count < adjudicateLossPlies ) {
7986                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7987
7988                 if( count & 1 ) {
7989                     score = -score; /* Flip score for winning side */
7990                 }
7991
7992                 if( score > adjudicateLossThreshold ) {
7993                     break;
7994                 }
7995
7996                 count++;
7997             }
7998
7999             if( count >= adjudicateLossPlies ) {
8000                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8001
8002                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8003                     "Xboard adjudication",
8004                     GE_XBOARD );
8005
8006                 return;
8007             }
8008         }
8009
8010         if(Adjudicate(cps)) {
8011             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8012             return; // [HGM] adjudicate: for all automatic game ends
8013         }
8014
8015 #if ZIPPY
8016         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8017             first.initDone) {
8018           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8019                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8020                 SendToICS("draw ");
8021                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8022           }
8023           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           ics_user_moved = 1;
8025           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8026                 char buf[3*MSG_SIZ];
8027
8028                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8029                         programStats.score / 100.,
8030                         programStats.depth,
8031                         programStats.time / 100.,
8032                         (unsigned int)programStats.nodes,
8033                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8034                         programStats.movelist);
8035                 SendToICS(buf);
8036 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8037           }
8038         }
8039 #endif
8040
8041         /* [AS] Clear stats for next move */
8042         ClearProgramStats();
8043         thinkOutput[0] = NULLCHAR;
8044         hiddenThinkOutputState = 0;
8045
8046         bookHit = NULL;
8047         if (gameMode == TwoMachinesPlay) {
8048             /* [HGM] relaying draw offers moved to after reception of move */
8049             /* and interpreting offer as claim if it brings draw condition */
8050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8051                 SendToProgram("draw\n", cps->other);
8052             }
8053             if (cps->other->sendTime) {
8054                 SendTimeRemaining(cps->other,
8055                                   cps->other->twoMachinesColor[0] == 'w');
8056             }
8057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8058             if (firstMove && !bookHit) {
8059                 firstMove = FALSE;
8060                 if (cps->other->useColors) {
8061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8062                 }
8063                 SendToProgram("go\n", cps->other);
8064             }
8065             cps->other->maybeThinking = TRUE;
8066         }
8067
8068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8069
8070         if (!pausing && appData.ringBellAfterMoves) {
8071             RingBell();
8072         }
8073
8074         /*
8075          * Reenable menu items that were disabled while
8076          * machine was thinking
8077          */
8078         if (gameMode != TwoMachinesPlay)
8079             SetUserThinkingEnables();
8080
8081         // [HGM] book: after book hit opponent has received move and is now in force mode
8082         // force the book reply into it, and then fake that it outputted this move by jumping
8083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8084         if(bookHit) {
8085                 static char bookMove[MSG_SIZ]; // a bit generous?
8086
8087                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8088                 strcat(bookMove, bookHit);
8089                 message = bookMove;
8090                 cps = cps->other;
8091                 programStats.nodes = programStats.depth = programStats.time =
8092                 programStats.score = programStats.got_only_move = 0;
8093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8094
8095                 if(cps->lastPing != cps->lastPong) {
8096                     savedMessage = message; // args for deferred call
8097                     savedState = cps;
8098                     ScheduleDelayedEvent(DeferredBookMove, 10);
8099                     return;
8100                 }
8101                 goto FakeBookMove;
8102         }
8103
8104         return;
8105     }
8106
8107     /* Set special modes for chess engines.  Later something general
8108      *  could be added here; for now there is just one kludge feature,
8109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8110      *  when "xboard" is given as an interactive command.
8111      */
8112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8113         cps->useSigint = FALSE;
8114         cps->useSigterm = FALSE;
8115     }
8116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8117       ParseFeatures(message+8, cps);
8118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8119     }
8120
8121     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8122                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8123       int dummy, s=6; char buf[MSG_SIZ];
8124       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8125       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8126       if(startedFromSetupPosition) return;
8127       ParseFEN(boards[0], &dummy, message+s);
8128       DrawPosition(TRUE, boards[0]);
8129       startedFromSetupPosition = TRUE;
8130       return;
8131     }
8132     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8133      * want this, I was asked to put it in, and obliged.
8134      */
8135     if (!strncmp(message, "setboard ", 9)) {
8136         Board initial_position;
8137
8138         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8139
8140         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8141             DisplayError(_("Bad FEN received from engine"), 0);
8142             return ;
8143         } else {
8144            Reset(TRUE, FALSE);
8145            CopyBoard(boards[0], initial_position);
8146            initialRulePlies = FENrulePlies;
8147            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8148            else gameMode = MachinePlaysBlack;
8149            DrawPosition(FALSE, boards[currentMove]);
8150         }
8151         return;
8152     }
8153
8154     /*
8155      * Look for communication commands
8156      */
8157     if (!strncmp(message, "telluser ", 9)) {
8158         if(message[9] == '\\' && message[10] == '\\')
8159             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8160         PlayTellSound();
8161         DisplayNote(message + 9);
8162         return;
8163     }
8164     if (!strncmp(message, "tellusererror ", 14)) {
8165         cps->userError = 1;
8166         if(message[14] == '\\' && message[15] == '\\')
8167             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8168         PlayTellSound();
8169         DisplayError(message + 14, 0);
8170         return;
8171     }
8172     if (!strncmp(message, "tellopponent ", 13)) {
8173       if (appData.icsActive) {
8174         if (loggedOn) {
8175           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8176           SendToICS(buf1);
8177         }
8178       } else {
8179         DisplayNote(message + 13);
8180       }
8181       return;
8182     }
8183     if (!strncmp(message, "tellothers ", 11)) {
8184       if (appData.icsActive) {
8185         if (loggedOn) {
8186           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8187           SendToICS(buf1);
8188         }
8189       }
8190       return;
8191     }
8192     if (!strncmp(message, "tellall ", 8)) {
8193       if (appData.icsActive) {
8194         if (loggedOn) {
8195           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8196           SendToICS(buf1);
8197         }
8198       } else {
8199         DisplayNote(message + 8);
8200       }
8201       return;
8202     }
8203     if (strncmp(message, "warning", 7) == 0) {
8204         /* Undocumented feature, use tellusererror in new code */
8205         DisplayError(message, 0);
8206         return;
8207     }
8208     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8209         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8210         strcat(realname, " query");
8211         AskQuestion(realname, buf2, buf1, cps->pr);
8212         return;
8213     }
8214     /* Commands from the engine directly to ICS.  We don't allow these to be
8215      *  sent until we are logged on. Crafty kibitzes have been known to
8216      *  interfere with the login process.
8217      */
8218     if (loggedOn) {
8219         if (!strncmp(message, "tellics ", 8)) {
8220             SendToICS(message + 8);
8221             SendToICS("\n");
8222             return;
8223         }
8224         if (!strncmp(message, "tellicsnoalias ", 15)) {
8225             SendToICS(ics_prefix);
8226             SendToICS(message + 15);
8227             SendToICS("\n");
8228             return;
8229         }
8230         /* The following are for backward compatibility only */
8231         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8232             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8233             SendToICS(ics_prefix);
8234             SendToICS(message);
8235             SendToICS("\n");
8236             return;
8237         }
8238     }
8239     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8240         return;
8241     }
8242     /*
8243      * If the move is illegal, cancel it and redraw the board.
8244      * Also deal with other error cases.  Matching is rather loose
8245      * here to accommodate engines written before the spec.
8246      */
8247     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8248         strncmp(message, "Error", 5) == 0) {
8249         if (StrStr(message, "name") ||
8250             StrStr(message, "rating") || StrStr(message, "?") ||
8251             StrStr(message, "result") || StrStr(message, "board") ||
8252             StrStr(message, "bk") || StrStr(message, "computer") ||
8253             StrStr(message, "variant") || StrStr(message, "hint") ||
8254             StrStr(message, "random") || StrStr(message, "depth") ||
8255             StrStr(message, "accepted")) {
8256             return;
8257         }
8258         if (StrStr(message, "protover")) {
8259           /* Program is responding to input, so it's apparently done
8260              initializing, and this error message indicates it is
8261              protocol version 1.  So we don't need to wait any longer
8262              for it to initialize and send feature commands. */
8263           FeatureDone(cps, 1);
8264           cps->protocolVersion = 1;
8265           return;
8266         }
8267         cps->maybeThinking = FALSE;
8268
8269         if (StrStr(message, "draw")) {
8270             /* Program doesn't have "draw" command */
8271             cps->sendDrawOffers = 0;
8272             return;
8273         }
8274         if (cps->sendTime != 1 &&
8275             (StrStr(message, "time") || StrStr(message, "otim"))) {
8276           /* Program apparently doesn't have "time" or "otim" command */
8277           cps->sendTime = 0;
8278           return;
8279         }
8280         if (StrStr(message, "analyze")) {
8281             cps->analysisSupport = FALSE;
8282             cps->analyzing = FALSE;
8283 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8284             EditGameEvent(); // [HGM] try to preserve loaded game
8285             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8286             DisplayError(buf2, 0);
8287             return;
8288         }
8289         if (StrStr(message, "(no matching move)st")) {
8290           /* Special kludge for GNU Chess 4 only */
8291           cps->stKludge = TRUE;
8292           SendTimeControl(cps, movesPerSession, timeControl,
8293                           timeIncrement, appData.searchDepth,
8294                           searchTime);
8295           return;
8296         }
8297         if (StrStr(message, "(no matching move)sd")) {
8298           /* Special kludge for GNU Chess 4 only */
8299           cps->sdKludge = TRUE;
8300           SendTimeControl(cps, movesPerSession, timeControl,
8301                           timeIncrement, appData.searchDepth,
8302                           searchTime);
8303           return;
8304         }
8305         if (!StrStr(message, "llegal")) {
8306             return;
8307         }
8308         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8309             gameMode == IcsIdle) return;
8310         if (forwardMostMove <= backwardMostMove) return;
8311         if (pausing) PauseEvent();
8312       if(appData.forceIllegal) {
8313             // [HGM] illegal: machine refused move; force position after move into it
8314           SendToProgram("force\n", cps);
8315           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8316                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8317                 // when black is to move, while there might be nothing on a2 or black
8318                 // might already have the move. So send the board as if white has the move.
8319                 // But first we must change the stm of the engine, as it refused the last move
8320                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8321                 if(WhiteOnMove(forwardMostMove)) {
8322                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8323                     SendBoard(cps, forwardMostMove); // kludgeless board
8324                 } else {
8325                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8326                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8327                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8328                 }
8329           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8330             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8331                  gameMode == TwoMachinesPlay)
8332               SendToProgram("go\n", cps);
8333             return;
8334       } else
8335         if (gameMode == PlayFromGameFile) {
8336             /* Stop reading this game file */
8337             gameMode = EditGame;
8338             ModeHighlight();
8339         }
8340         /* [HGM] illegal-move claim should forfeit game when Xboard */
8341         /* only passes fully legal moves                            */
8342         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8343             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8344                                 "False illegal-move claim", GE_XBOARD );
8345             return; // do not take back move we tested as valid
8346         }
8347         currentMove = forwardMostMove-1;
8348         DisplayMove(currentMove-1); /* before DisplayMoveError */
8349         SwitchClocks(forwardMostMove-1); // [HGM] race
8350         DisplayBothClocks();
8351         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8352                 parseList[currentMove], _(cps->which));
8353         DisplayMoveError(buf1);
8354         DrawPosition(FALSE, boards[currentMove]);
8355         return;
8356     }
8357     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8358         /* Program has a broken "time" command that
8359            outputs a string not ending in newline.
8360            Don't use it. */
8361         cps->sendTime = 0;
8362     }
8363
8364     /*
8365      * If chess program startup fails, exit with an error message.
8366      * Attempts to recover here are futile.
8367      */
8368     if ((StrStr(message, "unknown host") != NULL)
8369         || (StrStr(message, "No remote directory") != NULL)
8370         || (StrStr(message, "not found") != NULL)
8371         || (StrStr(message, "No such file") != NULL)
8372         || (StrStr(message, "can't alloc") != NULL)
8373         || (StrStr(message, "Permission denied") != NULL)) {
8374
8375         cps->maybeThinking = FALSE;
8376         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8377                 _(cps->which), cps->program, cps->host, message);
8378         RemoveInputSource(cps->isr);
8379         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8380             if(cps == &first) appData.noChessProgram = TRUE;
8381             DisplayError(buf1, 0);
8382         }
8383         return;
8384     }
8385
8386     /*
8387      * Look for hint output
8388      */
8389     if (sscanf(message, "Hint: %s", buf1) == 1) {
8390         if (cps == &first && hintRequested) {
8391             hintRequested = FALSE;
8392             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8393                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8394                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8395                                     PosFlags(forwardMostMove),
8396                                     fromY, fromX, toY, toX, promoChar, buf1);
8397                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8398                 DisplayInformation(buf2);
8399             } else {
8400                 /* Hint move could not be parsed!? */
8401               snprintf(buf2, sizeof(buf2),
8402                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8403                         buf1, _(cps->which));
8404                 DisplayError(buf2, 0);
8405             }
8406         } else {
8407           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8408         }
8409         return;
8410     }
8411
8412     /*
8413      * Ignore other messages if game is not in progress
8414      */
8415     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8416         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8417
8418     /*
8419      * look for win, lose, draw, or draw offer
8420      */
8421     if (strncmp(message, "1-0", 3) == 0) {
8422         char *p, *q, *r = "";
8423         p = strchr(message, '{');
8424         if (p) {
8425             q = strchr(p, '}');
8426             if (q) {
8427                 *q = NULLCHAR;
8428                 r = p + 1;
8429             }
8430         }
8431         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8432         return;
8433     } else if (strncmp(message, "0-1", 3) == 0) {
8434         char *p, *q, *r = "";
8435         p = strchr(message, '{');
8436         if (p) {
8437             q = strchr(p, '}');
8438             if (q) {
8439                 *q = NULLCHAR;
8440                 r = p + 1;
8441             }
8442         }
8443         /* Kludge for Arasan 4.1 bug */
8444         if (strcmp(r, "Black resigns") == 0) {
8445             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8446             return;
8447         }
8448         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8449         return;
8450     } else if (strncmp(message, "1/2", 3) == 0) {
8451         char *p, *q, *r = "";
8452         p = strchr(message, '{');
8453         if (p) {
8454             q = strchr(p, '}');
8455             if (q) {
8456                 *q = NULLCHAR;
8457                 r = p + 1;
8458             }
8459         }
8460
8461         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8462         return;
8463
8464     } else if (strncmp(message, "White resign", 12) == 0) {
8465         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strncmp(message, "Black resign", 12) == 0) {
8468         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8469         return;
8470     } else if (strncmp(message, "White matches", 13) == 0 ||
8471                strncmp(message, "Black matches", 13) == 0   ) {
8472         /* [HGM] ignore GNUShogi noises */
8473         return;
8474     } else if (strncmp(message, "White", 5) == 0 &&
8475                message[5] != '(' &&
8476                StrStr(message, "Black") == NULL) {
8477         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8478         return;
8479     } else if (strncmp(message, "Black", 5) == 0 &&
8480                message[5] != '(') {
8481         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8482         return;
8483     } else if (strcmp(message, "resign") == 0 ||
8484                strcmp(message, "computer resigns") == 0) {
8485         switch (gameMode) {
8486           case MachinePlaysBlack:
8487           case IcsPlayingBlack:
8488             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8489             break;
8490           case MachinePlaysWhite:
8491           case IcsPlayingWhite:
8492             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8493             break;
8494           case TwoMachinesPlay:
8495             if (cps->twoMachinesColor[0] == 'w')
8496               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8497             else
8498               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8499             break;
8500           default:
8501             /* can't happen */
8502             break;
8503         }
8504         return;
8505     } else if (strncmp(message, "opponent mates", 14) == 0) {
8506         switch (gameMode) {
8507           case MachinePlaysBlack:
8508           case IcsPlayingBlack:
8509             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8510             break;
8511           case MachinePlaysWhite:
8512           case IcsPlayingWhite:
8513             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8514             break;
8515           case TwoMachinesPlay:
8516             if (cps->twoMachinesColor[0] == 'w')
8517               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8518             else
8519               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8520             break;
8521           default:
8522             /* can't happen */
8523             break;
8524         }
8525         return;
8526     } else if (strncmp(message, "computer mates", 14) == 0) {
8527         switch (gameMode) {
8528           case MachinePlaysBlack:
8529           case IcsPlayingBlack:
8530             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8531             break;
8532           case MachinePlaysWhite:
8533           case IcsPlayingWhite:
8534             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8535             break;
8536           case TwoMachinesPlay:
8537             if (cps->twoMachinesColor[0] == 'w')
8538               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8539             else
8540               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8541             break;
8542           default:
8543             /* can't happen */
8544             break;
8545         }
8546         return;
8547     } else if (strncmp(message, "checkmate", 9) == 0) {
8548         if (WhiteOnMove(forwardMostMove)) {
8549             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8550         } else {
8551             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8552         }
8553         return;
8554     } else if (strstr(message, "Draw") != NULL ||
8555                strstr(message, "game is a draw") != NULL) {
8556         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8557         return;
8558     } else if (strstr(message, "offer") != NULL &&
8559                strstr(message, "draw") != NULL) {
8560 #if ZIPPY
8561         if (appData.zippyPlay && first.initDone) {
8562             /* Relay offer to ICS */
8563             SendToICS(ics_prefix);
8564             SendToICS("draw\n");
8565         }
8566 #endif
8567         cps->offeredDraw = 2; /* valid until this engine moves twice */
8568         if (gameMode == TwoMachinesPlay) {
8569             if (cps->other->offeredDraw) {
8570                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8571             /* [HGM] in two-machine mode we delay relaying draw offer      */
8572             /* until after we also have move, to see if it is really claim */
8573             }
8574         } else if (gameMode == MachinePlaysWhite ||
8575                    gameMode == MachinePlaysBlack) {
8576           if (userOfferedDraw) {
8577             DisplayInformation(_("Machine accepts your draw offer"));
8578             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8579           } else {
8580             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8581           }
8582         }
8583     }
8584
8585
8586     /*
8587      * Look for thinking output
8588      */
8589     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8590           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8591                                 ) {
8592         int plylev, mvleft, mvtot, curscore, time;
8593         char mvname[MOVE_LEN];
8594         u64 nodes; // [DM]
8595         char plyext;
8596         int ignore = FALSE;
8597         int prefixHint = FALSE;
8598         mvname[0] = NULLCHAR;
8599
8600         switch (gameMode) {
8601           case MachinePlaysBlack:
8602           case IcsPlayingBlack:
8603             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8604             break;
8605           case MachinePlaysWhite:
8606           case IcsPlayingWhite:
8607             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8608             break;
8609           case AnalyzeMode:
8610           case AnalyzeFile:
8611             break;
8612           case IcsObserving: /* [DM] icsEngineAnalyze */
8613             if (!appData.icsEngineAnalyze) ignore = TRUE;
8614             break;
8615           case TwoMachinesPlay:
8616             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8617                 ignore = TRUE;
8618             }
8619             break;
8620           default:
8621             ignore = TRUE;
8622             break;
8623         }
8624
8625         if (!ignore) {
8626             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8627             buf1[0] = NULLCHAR;
8628             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8629                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8630
8631                 if (plyext != ' ' && plyext != '\t') {
8632                     time *= 100;
8633                 }
8634
8635                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8636                 if( cps->scoreIsAbsolute &&
8637                     ( gameMode == MachinePlaysBlack ||
8638                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8639                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8640                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8641                      !WhiteOnMove(currentMove)
8642                     ) )
8643                 {
8644                     curscore = -curscore;
8645                 }
8646
8647                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8648
8649                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8650                         char buf[MSG_SIZ];
8651                         FILE *f;
8652                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8653                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8654                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8655                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8656                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8657                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8658                                 fclose(f);
8659                         } else DisplayError(_("failed writing PV"), 0);
8660                 }
8661
8662                 tempStats.depth = plylev;
8663                 tempStats.nodes = nodes;
8664                 tempStats.time = time;
8665                 tempStats.score = curscore;
8666                 tempStats.got_only_move = 0;
8667
8668                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8669                         int ticklen;
8670
8671                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8672                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8673                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8674                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8675                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8676                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8677                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8678                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8679                 }
8680
8681                 /* Buffer overflow protection */
8682                 if (pv[0] != NULLCHAR) {
8683                     if (strlen(pv) >= sizeof(tempStats.movelist)
8684                         && appData.debugMode) {
8685                         fprintf(debugFP,
8686                                 "PV is too long; using the first %u bytes.\n",
8687                                 (unsigned) sizeof(tempStats.movelist) - 1);
8688                     }
8689
8690                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8691                 } else {
8692                     sprintf(tempStats.movelist, " no PV\n");
8693                 }
8694
8695                 if (tempStats.seen_stat) {
8696                     tempStats.ok_to_send = 1;
8697                 }
8698
8699                 if (strchr(tempStats.movelist, '(') != NULL) {
8700                     tempStats.line_is_book = 1;
8701                     tempStats.nr_moves = 0;
8702                     tempStats.moves_left = 0;
8703                 } else {
8704                     tempStats.line_is_book = 0;
8705                 }
8706
8707                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8708                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8709
8710                 SendProgramStatsToFrontend( cps, &tempStats );
8711
8712                 /*
8713                     [AS] Protect the thinkOutput buffer from overflow... this
8714                     is only useful if buf1 hasn't overflowed first!
8715                 */
8716                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8717                          plylev,
8718                          (gameMode == TwoMachinesPlay ?
8719                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8720                          ((double) curscore) / 100.0,
8721                          prefixHint ? lastHint : "",
8722                          prefixHint ? " " : "" );
8723
8724                 if( buf1[0] != NULLCHAR ) {
8725                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8726
8727                     if( strlen(pv) > max_len ) {
8728                         if( appData.debugMode) {
8729                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8730                         }
8731                         pv[max_len+1] = '\0';
8732                     }
8733
8734                     strcat( thinkOutput, pv);
8735                 }
8736
8737                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8738                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8739                     DisplayMove(currentMove - 1);
8740                 }
8741                 return;
8742
8743             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8744                 /* crafty (9.25+) says "(only move) <move>"
8745                  * if there is only 1 legal move
8746                  */
8747                 sscanf(p, "(only move) %s", buf1);
8748                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8749                 sprintf(programStats.movelist, "%s (only move)", buf1);
8750                 programStats.depth = 1;
8751                 programStats.nr_moves = 1;
8752                 programStats.moves_left = 1;
8753                 programStats.nodes = 1;
8754                 programStats.time = 1;
8755                 programStats.got_only_move = 1;
8756
8757                 /* Not really, but we also use this member to
8758                    mean "line isn't going to change" (Crafty
8759                    isn't searching, so stats won't change) */
8760                 programStats.line_is_book = 1;
8761
8762                 SendProgramStatsToFrontend( cps, &programStats );
8763
8764                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8765                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8766                     DisplayMove(currentMove - 1);
8767                 }
8768                 return;
8769             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8770                               &time, &nodes, &plylev, &mvleft,
8771                               &mvtot, mvname) >= 5) {
8772                 /* The stat01: line is from Crafty (9.29+) in response
8773                    to the "." command */
8774                 programStats.seen_stat = 1;
8775                 cps->maybeThinking = TRUE;
8776
8777                 if (programStats.got_only_move || !appData.periodicUpdates)
8778                   return;
8779
8780                 programStats.depth = plylev;
8781                 programStats.time = time;
8782                 programStats.nodes = nodes;
8783                 programStats.moves_left = mvleft;
8784                 programStats.nr_moves = mvtot;
8785                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8786                 programStats.ok_to_send = 1;
8787                 programStats.movelist[0] = '\0';
8788
8789                 SendProgramStatsToFrontend( cps, &programStats );
8790
8791                 return;
8792
8793             } else if (strncmp(message,"++",2) == 0) {
8794                 /* Crafty 9.29+ outputs this */
8795                 programStats.got_fail = 2;
8796                 return;
8797
8798             } else if (strncmp(message,"--",2) == 0) {
8799                 /* Crafty 9.29+ outputs this */
8800                 programStats.got_fail = 1;
8801                 return;
8802
8803             } else if (thinkOutput[0] != NULLCHAR &&
8804                        strncmp(message, "    ", 4) == 0) {
8805                 unsigned message_len;
8806
8807                 p = message;
8808                 while (*p && *p == ' ') p++;
8809
8810                 message_len = strlen( p );
8811
8812                 /* [AS] Avoid buffer overflow */
8813                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8814                     strcat(thinkOutput, " ");
8815                     strcat(thinkOutput, p);
8816                 }
8817
8818                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8819                     strcat(programStats.movelist, " ");
8820                     strcat(programStats.movelist, p);
8821                 }
8822
8823                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8824                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8825                     DisplayMove(currentMove - 1);
8826                 }
8827                 return;
8828             }
8829         }
8830         else {
8831             buf1[0] = NULLCHAR;
8832
8833             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8834                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8835             {
8836                 ChessProgramStats cpstats;
8837
8838                 if (plyext != ' ' && plyext != '\t') {
8839                     time *= 100;
8840                 }
8841
8842                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8843                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8844                     curscore = -curscore;
8845                 }
8846
8847                 cpstats.depth = plylev;
8848                 cpstats.nodes = nodes;
8849                 cpstats.time = time;
8850                 cpstats.score = curscore;
8851                 cpstats.got_only_move = 0;
8852                 cpstats.movelist[0] = '\0';
8853
8854                 if (buf1[0] != NULLCHAR) {
8855                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8856                 }
8857
8858                 cpstats.ok_to_send = 0;
8859                 cpstats.line_is_book = 0;
8860                 cpstats.nr_moves = 0;
8861                 cpstats.moves_left = 0;
8862
8863                 SendProgramStatsToFrontend( cps, &cpstats );
8864             }
8865         }
8866     }
8867 }
8868
8869
8870 /* Parse a game score from the character string "game", and
8871    record it as the history of the current game.  The game
8872    score is NOT assumed to start from the standard position.
8873    The display is not updated in any way.
8874    */
8875 void
8876 ParseGameHistory(game)
8877      char *game;
8878 {
8879     ChessMove moveType;
8880     int fromX, fromY, toX, toY, boardIndex;
8881     char promoChar;
8882     char *p, *q;
8883     char buf[MSG_SIZ];
8884
8885     if (appData.debugMode)
8886       fprintf(debugFP, "Parsing game history: %s\n", game);
8887
8888     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8889     gameInfo.site = StrSave(appData.icsHost);
8890     gameInfo.date = PGNDate();
8891     gameInfo.round = StrSave("-");
8892
8893     /* Parse out names of players */
8894     while (*game == ' ') game++;
8895     p = buf;
8896     while (*game != ' ') *p++ = *game++;
8897     *p = NULLCHAR;
8898     gameInfo.white = StrSave(buf);
8899     while (*game == ' ') game++;
8900     p = buf;
8901     while (*game != ' ' && *game != '\n') *p++ = *game++;
8902     *p = NULLCHAR;
8903     gameInfo.black = StrSave(buf);
8904
8905     /* Parse moves */
8906     boardIndex = blackPlaysFirst ? 1 : 0;
8907     yynewstr(game);
8908     for (;;) {
8909         yyboardindex = boardIndex;
8910         moveType = (ChessMove) Myylex();
8911         switch (moveType) {
8912           case IllegalMove:             /* maybe suicide chess, etc. */
8913   if (appData.debugMode) {
8914     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8915     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8916     setbuf(debugFP, NULL);
8917   }
8918           case WhitePromotion:
8919           case BlackPromotion:
8920           case WhiteNonPromotion:
8921           case BlackNonPromotion:
8922           case NormalMove:
8923           case WhiteCapturesEnPassant:
8924           case BlackCapturesEnPassant:
8925           case WhiteKingSideCastle:
8926           case WhiteQueenSideCastle:
8927           case BlackKingSideCastle:
8928           case BlackQueenSideCastle:
8929           case WhiteKingSideCastleWild:
8930           case WhiteQueenSideCastleWild:
8931           case BlackKingSideCastleWild:
8932           case BlackQueenSideCastleWild:
8933           /* PUSH Fabien */
8934           case WhiteHSideCastleFR:
8935           case WhiteASideCastleFR:
8936           case BlackHSideCastleFR:
8937           case BlackASideCastleFR:
8938           /* POP Fabien */
8939             fromX = currentMoveString[0] - AAA;
8940             fromY = currentMoveString[1] - ONE;
8941             toX = currentMoveString[2] - AAA;
8942             toY = currentMoveString[3] - ONE;
8943             promoChar = currentMoveString[4];
8944             break;
8945           case WhiteDrop:
8946           case BlackDrop:
8947             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8948             fromX = moveType == WhiteDrop ?
8949               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8950             (int) CharToPiece(ToLower(currentMoveString[0]));
8951             fromY = DROP_RANK;
8952             toX = currentMoveString[2] - AAA;
8953             toY = currentMoveString[3] - ONE;
8954             promoChar = NULLCHAR;
8955             break;
8956           case AmbiguousMove:
8957             /* bug? */
8958             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8959   if (appData.debugMode) {
8960     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8961     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8962     setbuf(debugFP, NULL);
8963   }
8964             DisplayError(buf, 0);
8965             return;
8966           case ImpossibleMove:
8967             /* bug? */
8968             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8969   if (appData.debugMode) {
8970     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8971     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8972     setbuf(debugFP, NULL);
8973   }
8974             DisplayError(buf, 0);
8975             return;
8976           case EndOfFile:
8977             if (boardIndex < backwardMostMove) {
8978                 /* Oops, gap.  How did that happen? */
8979                 DisplayError(_("Gap in move list"), 0);
8980                 return;
8981             }
8982             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8983             if (boardIndex > forwardMostMove) {
8984                 forwardMostMove = boardIndex;
8985             }
8986             return;
8987           case ElapsedTime:
8988             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8989                 strcat(parseList[boardIndex-1], " ");
8990                 strcat(parseList[boardIndex-1], yy_text);
8991             }
8992             continue;
8993           case Comment:
8994           case PGNTag:
8995           case NAG:
8996           default:
8997             /* ignore */
8998             continue;
8999           case WhiteWins:
9000           case BlackWins:
9001           case GameIsDrawn:
9002           case GameUnfinished:
9003             if (gameMode == IcsExamining) {
9004                 if (boardIndex < backwardMostMove) {
9005                     /* Oops, gap.  How did that happen? */
9006                     return;
9007                 }
9008                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9009                 return;
9010             }
9011             gameInfo.result = moveType;
9012             p = strchr(yy_text, '{');
9013             if (p == NULL) p = strchr(yy_text, '(');
9014             if (p == NULL) {
9015                 p = yy_text;
9016                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9017             } else {
9018                 q = strchr(p, *p == '{' ? '}' : ')');
9019                 if (q != NULL) *q = NULLCHAR;
9020                 p++;
9021             }
9022             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9023             gameInfo.resultDetails = StrSave(p);
9024             continue;
9025         }
9026         if (boardIndex >= forwardMostMove &&
9027             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9028             backwardMostMove = blackPlaysFirst ? 1 : 0;
9029             return;
9030         }
9031         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9032                                  fromY, fromX, toY, toX, promoChar,
9033                                  parseList[boardIndex]);
9034         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9035         /* currentMoveString is set as a side-effect of yylex */
9036         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9037         strcat(moveList[boardIndex], "\n");
9038         boardIndex++;
9039         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9040         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9041           case MT_NONE:
9042           case MT_STALEMATE:
9043           default:
9044             break;
9045           case MT_CHECK:
9046             if(gameInfo.variant != VariantShogi)
9047                 strcat(parseList[boardIndex - 1], "+");
9048             break;
9049           case MT_CHECKMATE:
9050           case MT_STAINMATE:
9051             strcat(parseList[boardIndex - 1], "#");
9052             break;
9053         }
9054     }
9055 }
9056
9057
9058 /* Apply a move to the given board  */
9059 void
9060 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9061      int fromX, fromY, toX, toY;
9062      int promoChar;
9063      Board board;
9064 {
9065   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9066   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9067
9068     /* [HGM] compute & store e.p. status and castling rights for new position */
9069     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9070
9071       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9072       oldEP = (signed char)board[EP_STATUS];
9073       board[EP_STATUS] = EP_NONE;
9074
9075   if (fromY == DROP_RANK) {
9076         /* must be first */
9077         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9078             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9079             return;
9080         }
9081         piece = board[toY][toX] = (ChessSquare) fromX;
9082   } else {
9083       int i;
9084
9085       if( board[toY][toX] != EmptySquare )
9086            board[EP_STATUS] = EP_CAPTURE;
9087
9088       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9089            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9090                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9091       } else
9092       if( board[fromY][fromX] == WhitePawn ) {
9093            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9094                board[EP_STATUS] = EP_PAWN_MOVE;
9095            if( toY-fromY==2) {
9096                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9097                         gameInfo.variant != VariantBerolina || toX < fromX)
9098                       board[EP_STATUS] = toX | berolina;
9099                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9100                         gameInfo.variant != VariantBerolina || toX > fromX)
9101                       board[EP_STATUS] = toX;
9102            }
9103       } else
9104       if( board[fromY][fromX] == BlackPawn ) {
9105            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9106                board[EP_STATUS] = EP_PAWN_MOVE;
9107            if( toY-fromY== -2) {
9108                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9109                         gameInfo.variant != VariantBerolina || toX < fromX)
9110                       board[EP_STATUS] = toX | berolina;
9111                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9112                         gameInfo.variant != VariantBerolina || toX > fromX)
9113                       board[EP_STATUS] = toX;
9114            }
9115        }
9116
9117        for(i=0; i<nrCastlingRights; i++) {
9118            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9119               board[CASTLING][i] == toX   && castlingRank[i] == toY
9120              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9121        }
9122
9123      if (fromX == toX && fromY == toY) return;
9124
9125      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9126      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9127      if(gameInfo.variant == VariantKnightmate)
9128          king += (int) WhiteUnicorn - (int) WhiteKing;
9129
9130     /* Code added by Tord: */
9131     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9132     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9133         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9134       board[fromY][fromX] = EmptySquare;
9135       board[toY][toX] = EmptySquare;
9136       if((toX > fromX) != (piece == WhiteRook)) {
9137         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9138       } else {
9139         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9140       }
9141     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9142                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9143       board[fromY][fromX] = EmptySquare;
9144       board[toY][toX] = EmptySquare;
9145       if((toX > fromX) != (piece == BlackRook)) {
9146         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9147       } else {
9148         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9149       }
9150     /* End of code added by Tord */
9151
9152     } else if (board[fromY][fromX] == king
9153         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9154         && toY == fromY && toX > fromX+1) {
9155         board[fromY][fromX] = EmptySquare;
9156         board[toY][toX] = king;
9157         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9158         board[fromY][BOARD_RGHT-1] = EmptySquare;
9159     } else if (board[fromY][fromX] == king
9160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9161                && toY == fromY && toX < fromX-1) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = king;
9164         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9165         board[fromY][BOARD_LEFT] = EmptySquare;
9166     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9167                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9168                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9169                ) {
9170         /* white pawn promotion */
9171         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9172         if(gameInfo.variant==VariantBughouse ||
9173            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9174             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9175         board[fromY][fromX] = EmptySquare;
9176     } else if ((fromY >= BOARD_HEIGHT>>1)
9177                && (toX != fromX)
9178                && gameInfo.variant != VariantXiangqi
9179                && gameInfo.variant != VariantBerolina
9180                && (board[fromY][fromX] == WhitePawn)
9181                && (board[toY][toX] == EmptySquare)) {
9182         board[fromY][fromX] = EmptySquare;
9183         board[toY][toX] = WhitePawn;
9184         captured = board[toY - 1][toX];
9185         board[toY - 1][toX] = EmptySquare;
9186     } else if ((fromY == BOARD_HEIGHT-4)
9187                && (toX == fromX)
9188                && gameInfo.variant == VariantBerolina
9189                && (board[fromY][fromX] == WhitePawn)
9190                && (board[toY][toX] == EmptySquare)) {
9191         board[fromY][fromX] = EmptySquare;
9192         board[toY][toX] = WhitePawn;
9193         if(oldEP & EP_BEROLIN_A) {
9194                 captured = board[fromY][fromX-1];
9195                 board[fromY][fromX-1] = EmptySquare;
9196         }else{  captured = board[fromY][fromX+1];
9197                 board[fromY][fromX+1] = EmptySquare;
9198         }
9199     } else if (board[fromY][fromX] == king
9200         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9201                && toY == fromY && toX > fromX+1) {
9202         board[fromY][fromX] = EmptySquare;
9203         board[toY][toX] = king;
9204         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9205         board[fromY][BOARD_RGHT-1] = EmptySquare;
9206     } else if (board[fromY][fromX] == king
9207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9208                && toY == fromY && toX < fromX-1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = king;
9211         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9212         board[fromY][BOARD_LEFT] = EmptySquare;
9213     } else if (fromY == 7 && fromX == 3
9214                && board[fromY][fromX] == BlackKing
9215                && toY == 7 && toX == 5) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = BlackKing;
9218         board[fromY][7] = EmptySquare;
9219         board[toY][4] = BlackRook;
9220     } else if (fromY == 7 && fromX == 3
9221                && board[fromY][fromX] == BlackKing
9222                && toY == 7 && toX == 1) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackKing;
9225         board[fromY][0] = EmptySquare;
9226         board[toY][2] = BlackRook;
9227     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9228                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9229                && toY < promoRank && promoChar
9230                ) {
9231         /* black pawn promotion */
9232         board[toY][toX] = CharToPiece(ToLower(promoChar));
9233         if(gameInfo.variant==VariantBughouse ||
9234            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9235             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9236         board[fromY][fromX] = EmptySquare;
9237     } else if ((fromY < BOARD_HEIGHT>>1)
9238                && (toX != fromX)
9239                && gameInfo.variant != VariantXiangqi
9240                && gameInfo.variant != VariantBerolina
9241                && (board[fromY][fromX] == BlackPawn)
9242                && (board[toY][toX] == EmptySquare)) {
9243         board[fromY][fromX] = EmptySquare;
9244         board[toY][toX] = BlackPawn;
9245         captured = board[toY + 1][toX];
9246         board[toY + 1][toX] = EmptySquare;
9247     } else if ((fromY == 3)
9248                && (toX == fromX)
9249                && gameInfo.variant == VariantBerolina
9250                && (board[fromY][fromX] == BlackPawn)
9251                && (board[toY][toX] == EmptySquare)) {
9252         board[fromY][fromX] = EmptySquare;
9253         board[toY][toX] = BlackPawn;
9254         if(oldEP & EP_BEROLIN_A) {
9255                 captured = board[fromY][fromX-1];
9256                 board[fromY][fromX-1] = EmptySquare;
9257         }else{  captured = board[fromY][fromX+1];
9258                 board[fromY][fromX+1] = EmptySquare;
9259         }
9260     } else {
9261         board[toY][toX] = board[fromY][fromX];
9262         board[fromY][fromX] = EmptySquare;
9263     }
9264   }
9265
9266     if (gameInfo.holdingsWidth != 0) {
9267
9268       /* !!A lot more code needs to be written to support holdings  */
9269       /* [HGM] OK, so I have written it. Holdings are stored in the */
9270       /* penultimate board files, so they are automaticlly stored   */
9271       /* in the game history.                                       */
9272       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9273                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9274         /* Delete from holdings, by decreasing count */
9275         /* and erasing image if necessary            */
9276         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9277         if(p < (int) BlackPawn) { /* white drop */
9278              p -= (int)WhitePawn;
9279                  p = PieceToNumber((ChessSquare)p);
9280              if(p >= gameInfo.holdingsSize) p = 0;
9281              if(--board[p][BOARD_WIDTH-2] <= 0)
9282                   board[p][BOARD_WIDTH-1] = EmptySquare;
9283              if((int)board[p][BOARD_WIDTH-2] < 0)
9284                         board[p][BOARD_WIDTH-2] = 0;
9285         } else {                  /* black drop */
9286              p -= (int)BlackPawn;
9287                  p = PieceToNumber((ChessSquare)p);
9288              if(p >= gameInfo.holdingsSize) p = 0;
9289              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9290                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9291              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9292                         board[BOARD_HEIGHT-1-p][1] = 0;
9293         }
9294       }
9295       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9296           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9297         /* [HGM] holdings: Add to holdings, if holdings exist */
9298         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9299                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9300                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9301         }
9302         p = (int) captured;
9303         if (p >= (int) BlackPawn) {
9304           p -= (int)BlackPawn;
9305           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9306                   /* in Shogi restore piece to its original  first */
9307                   captured = (ChessSquare) (DEMOTED captured);
9308                   p = DEMOTED p;
9309           }
9310           p = PieceToNumber((ChessSquare)p);
9311           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9312           board[p][BOARD_WIDTH-2]++;
9313           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9314         } else {
9315           p -= (int)WhitePawn;
9316           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9317                   captured = (ChessSquare) (DEMOTED captured);
9318                   p = DEMOTED p;
9319           }
9320           p = PieceToNumber((ChessSquare)p);
9321           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9322           board[BOARD_HEIGHT-1-p][1]++;
9323           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9324         }
9325       }
9326     } else if (gameInfo.variant == VariantAtomic) {
9327       if (captured != EmptySquare) {
9328         int y, x;
9329         for (y = toY-1; y <= toY+1; y++) {
9330           for (x = toX-1; x <= toX+1; x++) {
9331             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9332                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9333               board[y][x] = EmptySquare;
9334             }
9335           }
9336         }
9337         board[toY][toX] = EmptySquare;
9338       }
9339     }
9340     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9341         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9342     } else
9343     if(promoChar == '+') {
9344         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9345         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9346     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9347         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9348     }
9349     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9350                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9351         // [HGM] superchess: take promotion piece out of holdings
9352         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9353         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9354             if(!--board[k][BOARD_WIDTH-2])
9355                 board[k][BOARD_WIDTH-1] = EmptySquare;
9356         } else {
9357             if(!--board[BOARD_HEIGHT-1-k][1])
9358                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9359         }
9360     }
9361
9362 }
9363
9364 /* Updates forwardMostMove */
9365 void
9366 MakeMove(fromX, fromY, toX, toY, promoChar)
9367      int fromX, fromY, toX, toY;
9368      int promoChar;
9369 {
9370 //    forwardMostMove++; // [HGM] bare: moved downstream
9371
9372     (void) CoordsToAlgebraic(boards[forwardMostMove],
9373                              PosFlags(forwardMostMove),
9374                              fromY, fromX, toY, toX, promoChar,
9375                              parseList[forwardMostMove]);
9376
9377     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9378         int timeLeft; static int lastLoadFlag=0; int king, piece;
9379         piece = boards[forwardMostMove][fromY][fromX];
9380         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9381         if(gameInfo.variant == VariantKnightmate)
9382             king += (int) WhiteUnicorn - (int) WhiteKing;
9383         if(forwardMostMove == 0) {
9384             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9385                 fprintf(serverMoves, "%s;", UserName());
9386             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9387                 fprintf(serverMoves, "%s;", second.tidy);
9388             fprintf(serverMoves, "%s;", first.tidy);
9389             if(gameMode == MachinePlaysWhite)
9390                 fprintf(serverMoves, "%s;", UserName());
9391             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9392                 fprintf(serverMoves, "%s;", second.tidy);
9393         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9394         lastLoadFlag = loadFlag;
9395         // print base move
9396         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9397         // print castling suffix
9398         if( toY == fromY && piece == king ) {
9399             if(toX-fromX > 1)
9400                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9401             if(fromX-toX >1)
9402                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9403         }
9404         // e.p. suffix
9405         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9406              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9407              boards[forwardMostMove][toY][toX] == EmptySquare
9408              && fromX != toX && fromY != toY)
9409                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9410         // promotion suffix
9411         if(promoChar != NULLCHAR)
9412                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9413         if(!loadFlag) {
9414                 char buf[MOVE_LEN*2], *p; int len;
9415             fprintf(serverMoves, "/%d/%d",
9416                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9417             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9418             else                      timeLeft = blackTimeRemaining/1000;
9419             fprintf(serverMoves, "/%d", timeLeft);
9420                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9421                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9422                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9423             fprintf(serverMoves, "/%s", buf);
9424         }
9425         fflush(serverMoves);
9426     }
9427
9428     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9429         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9430       return;
9431     }
9432     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9433     if (commentList[forwardMostMove+1] != NULL) {
9434         free(commentList[forwardMostMove+1]);
9435         commentList[forwardMostMove+1] = NULL;
9436     }
9437     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9438     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9439     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9440     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9441     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9442     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9443     adjustedClock = FALSE;
9444     gameInfo.result = GameUnfinished;
9445     if (gameInfo.resultDetails != NULL) {
9446         free(gameInfo.resultDetails);
9447         gameInfo.resultDetails = NULL;
9448     }
9449     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9450                               moveList[forwardMostMove - 1]);
9451     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9452       case MT_NONE:
9453       case MT_STALEMATE:
9454       default:
9455         break;
9456       case MT_CHECK:
9457         if(gameInfo.variant != VariantShogi)
9458             strcat(parseList[forwardMostMove - 1], "+");
9459         break;
9460       case MT_CHECKMATE:
9461       case MT_STAINMATE:
9462         strcat(parseList[forwardMostMove - 1], "#");
9463         break;
9464     }
9465     if (appData.debugMode) {
9466         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9467     }
9468
9469 }
9470
9471 /* Updates currentMove if not pausing */
9472 void
9473 ShowMove(fromX, fromY, toX, toY)
9474 {
9475     int instant = (gameMode == PlayFromGameFile) ?
9476         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9477     if(appData.noGUI) return;
9478     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9479         if (!instant) {
9480             if (forwardMostMove == currentMove + 1) {
9481                 AnimateMove(boards[forwardMostMove - 1],
9482                             fromX, fromY, toX, toY);
9483             }
9484             if (appData.highlightLastMove) {
9485                 SetHighlights(fromX, fromY, toX, toY);
9486             }
9487         }
9488         currentMove = forwardMostMove;
9489     }
9490
9491     if (instant) return;
9492
9493     DisplayMove(currentMove - 1);
9494     DrawPosition(FALSE, boards[currentMove]);
9495     DisplayBothClocks();
9496     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9497 }
9498
9499 void SendEgtPath(ChessProgramState *cps)
9500 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9501         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9502
9503         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9504
9505         while(*p) {
9506             char c, *q = name+1, *r, *s;
9507
9508             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9509             while(*p && *p != ',') *q++ = *p++;
9510             *q++ = ':'; *q = 0;
9511             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9512                 strcmp(name, ",nalimov:") == 0 ) {
9513                 // take nalimov path from the menu-changeable option first, if it is defined
9514               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9515                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9516             } else
9517             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9518                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9519                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9520                 s = r = StrStr(s, ":") + 1; // beginning of path info
9521                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9522                 c = *r; *r = 0;             // temporarily null-terminate path info
9523                     *--q = 0;               // strip of trailig ':' from name
9524                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9525                 *r = c;
9526                 SendToProgram(buf,cps);     // send egtbpath command for this format
9527             }
9528             if(*p == ',') p++; // read away comma to position for next format name
9529         }
9530 }
9531
9532 void
9533 InitChessProgram(cps, setup)
9534      ChessProgramState *cps;
9535      int setup; /* [HGM] needed to setup FRC opening position */
9536 {
9537     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9538     if (appData.noChessProgram) return;
9539     hintRequested = FALSE;
9540     bookRequested = FALSE;
9541
9542     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9543     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9544     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9545     if(cps->memSize) { /* [HGM] memory */
9546       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9547         SendToProgram(buf, cps);
9548     }
9549     SendEgtPath(cps); /* [HGM] EGT */
9550     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9551       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9552         SendToProgram(buf, cps);
9553     }
9554
9555     SendToProgram(cps->initString, cps);
9556     if (gameInfo.variant != VariantNormal &&
9557         gameInfo.variant != VariantLoadable
9558         /* [HGM] also send variant if board size non-standard */
9559         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9560                                             ) {
9561       char *v = VariantName(gameInfo.variant);
9562       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9563         /* [HGM] in protocol 1 we have to assume all variants valid */
9564         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9565         DisplayFatalError(buf, 0, 1);
9566         return;
9567       }
9568
9569       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9570       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9571       if( gameInfo.variant == VariantXiangqi )
9572            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9573       if( gameInfo.variant == VariantShogi )
9574            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9575       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9576            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9577       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9578           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9579            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9580       if( gameInfo.variant == VariantCourier )
9581            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9582       if( gameInfo.variant == VariantSuper )
9583            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9584       if( gameInfo.variant == VariantGreat )
9585            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9586       if( gameInfo.variant == VariantSChess )
9587            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9588       if( gameInfo.variant == VariantGrand )
9589            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9590
9591       if(overruled) {
9592         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9593                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9594            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9595            if(StrStr(cps->variants, b) == NULL) {
9596                // specific sized variant not known, check if general sizing allowed
9597                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9598                    if(StrStr(cps->variants, "boardsize") == NULL) {
9599                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9600                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9601                        DisplayFatalError(buf, 0, 1);
9602                        return;
9603                    }
9604                    /* [HGM] here we really should compare with the maximum supported board size */
9605                }
9606            }
9607       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9608       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9609       SendToProgram(buf, cps);
9610     }
9611     currentlyInitializedVariant = gameInfo.variant;
9612
9613     /* [HGM] send opening position in FRC to first engine */
9614     if(setup) {
9615           SendToProgram("force\n", cps);
9616           SendBoard(cps, 0);
9617           /* engine is now in force mode! Set flag to wake it up after first move. */
9618           setboardSpoiledMachineBlack = 1;
9619     }
9620
9621     if (cps->sendICS) {
9622       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9623       SendToProgram(buf, cps);
9624     }
9625     cps->maybeThinking = FALSE;
9626     cps->offeredDraw = 0;
9627     if (!appData.icsActive) {
9628         SendTimeControl(cps, movesPerSession, timeControl,
9629                         timeIncrement, appData.searchDepth,
9630                         searchTime);
9631     }
9632     if (appData.showThinking
9633         // [HGM] thinking: four options require thinking output to be sent
9634         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9635                                 ) {
9636         SendToProgram("post\n", cps);
9637     }
9638     SendToProgram("hard\n", cps);
9639     if (!appData.ponderNextMove) {
9640         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9641            it without being sure what state we are in first.  "hard"
9642            is not a toggle, so that one is OK.
9643          */
9644         SendToProgram("easy\n", cps);
9645     }
9646     if (cps->usePing) {
9647       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9648       SendToProgram(buf, cps);
9649     }
9650     cps->initDone = TRUE;
9651     ClearEngineOutputPane(cps == &second);
9652 }
9653
9654
9655 void
9656 StartChessProgram(cps)
9657      ChessProgramState *cps;
9658 {
9659     char buf[MSG_SIZ];
9660     int err;
9661
9662     if (appData.noChessProgram) return;
9663     cps->initDone = FALSE;
9664
9665     if (strcmp(cps->host, "localhost") == 0) {
9666         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9667     } else if (*appData.remoteShell == NULLCHAR) {
9668         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9669     } else {
9670         if (*appData.remoteUser == NULLCHAR) {
9671           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9672                     cps->program);
9673         } else {
9674           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9675                     cps->host, appData.remoteUser, cps->program);
9676         }
9677         err = StartChildProcess(buf, "", &cps->pr);
9678     }
9679
9680     if (err != 0) {
9681       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9682         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9683         if(cps != &first) return;
9684         appData.noChessProgram = TRUE;
9685         ThawUI();
9686         SetNCPMode();
9687 //      DisplayFatalError(buf, err, 1);
9688 //      cps->pr = NoProc;
9689 //      cps->isr = NULL;
9690         return;
9691     }
9692
9693     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9694     if (cps->protocolVersion > 1) {
9695       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9696       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9697       cps->comboCnt = 0;  //                and values of combo boxes
9698       SendToProgram(buf, cps);
9699     } else {
9700       SendToProgram("xboard\n", cps);
9701     }
9702 }
9703
9704 void
9705 TwoMachinesEventIfReady P((void))
9706 {
9707   static int curMess = 0;
9708   if (first.lastPing != first.lastPong) {
9709     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9710     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9711     return;
9712   }
9713   if (second.lastPing != second.lastPong) {
9714     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9715     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9716     return;
9717   }
9718   DisplayMessage("", ""); curMess = 0;
9719   ThawUI();
9720   TwoMachinesEvent();
9721 }
9722
9723 char *
9724 MakeName(char *template)
9725 {
9726     time_t clock;
9727     struct tm *tm;
9728     static char buf[MSG_SIZ];
9729     char *p = buf;
9730     int i;
9731
9732     clock = time((time_t *)NULL);
9733     tm = localtime(&clock);
9734
9735     while(*p++ = *template++) if(p[-1] == '%') {
9736         switch(*template++) {
9737           case 0:   *p = 0; return buf;
9738           case 'Y': i = tm->tm_year+1900; break;
9739           case 'y': i = tm->tm_year-100; break;
9740           case 'M': i = tm->tm_mon+1; break;
9741           case 'd': i = tm->tm_mday; break;
9742           case 'h': i = tm->tm_hour; break;
9743           case 'm': i = tm->tm_min; break;
9744           case 's': i = tm->tm_sec; break;
9745           default:  i = 0;
9746         }
9747         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9748     }
9749     return buf;
9750 }
9751
9752 int
9753 CountPlayers(char *p)
9754 {
9755     int n = 0;
9756     while(p = strchr(p, '\n')) p++, n++; // count participants
9757     return n;
9758 }
9759
9760 FILE *
9761 WriteTourneyFile(char *results, FILE *f)
9762 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9763     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9764     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9765         // create a file with tournament description
9766         fprintf(f, "-participants {%s}\n", appData.participants);
9767         fprintf(f, "-seedBase %d\n", appData.seedBase);
9768         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9769         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9770         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9771         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9772         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9773         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9774         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9775         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9776         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9777         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9778         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9779         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9780         if(searchTime > 0)
9781                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9782         else {
9783                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9784                 fprintf(f, "-tc %s\n", appData.timeControl);
9785                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9786         }
9787         fprintf(f, "-results \"%s\"\n", results);
9788     }
9789     return f;
9790 }
9791
9792 #define MAXENGINES 1000
9793 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9794
9795 void Substitute(char *participants, int expunge)
9796 {
9797     int i, changed, changes=0, nPlayers=0;
9798     char *p, *q, *r, buf[MSG_SIZ];
9799     if(participants == NULL) return;
9800     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9801     r = p = participants; q = appData.participants;
9802     while(*p && *p == *q) {
9803         if(*p == '\n') r = p+1, nPlayers++;
9804         p++; q++;
9805     }
9806     if(*p) { // difference
9807         while(*p && *p++ != '\n');
9808         while(*q && *q++ != '\n');
9809       changed = nPlayers;
9810         changes = 1 + (strcmp(p, q) != 0);
9811     }
9812     if(changes == 1) { // a single engine mnemonic was changed
9813         q = r; while(*q) nPlayers += (*q++ == '\n');
9814         p = buf; while(*r && (*p = *r++) != '\n') p++;
9815         *p = NULLCHAR;
9816         NamesToList(firstChessProgramNames, command, mnemonic);
9817         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9818         if(mnemonic[i]) { // The substitute is valid
9819             FILE *f;
9820             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9821                 flock(fileno(f), LOCK_EX);
9822                 ParseArgsFromFile(f);
9823                 fseek(f, 0, SEEK_SET);
9824                 FREE(appData.participants); appData.participants = participants;
9825                 if(expunge) { // erase results of replaced engine
9826                     int len = strlen(appData.results), w, b, dummy;
9827                     for(i=0; i<len; i++) {
9828                         Pairing(i, nPlayers, &w, &b, &dummy);
9829                         if((w == changed || b == changed) && appData.results[i] == '*') {
9830                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9831                             fclose(f);
9832                             return;
9833                         }
9834                     }
9835                     for(i=0; i<len; i++) {
9836                         Pairing(i, nPlayers, &w, &b, &dummy);
9837                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9838                     }
9839                 }
9840                 WriteTourneyFile(appData.results, f);
9841                 fclose(f); // release lock
9842                 return;
9843             }
9844         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9845     }
9846     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9847     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9848     free(participants);
9849     return;
9850 }
9851
9852 int
9853 CreateTourney(char *name)
9854 {
9855         FILE *f;
9856         if(matchMode && strcmp(name, appData.tourneyFile)) {
9857              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9858         }
9859         if(name[0] == NULLCHAR) {
9860             if(appData.participants[0])
9861                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9862             return 0;
9863         }
9864         f = fopen(name, "r");
9865         if(f) { // file exists
9866             ASSIGN(appData.tourneyFile, name);
9867             ParseArgsFromFile(f); // parse it
9868         } else {
9869             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9870             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9871                 DisplayError(_("Not enough participants"), 0);
9872                 return 0;
9873             }
9874             ASSIGN(appData.tourneyFile, name);
9875             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9876             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9877         }
9878         fclose(f);
9879         appData.noChessProgram = FALSE;
9880         appData.clockMode = TRUE;
9881         SetGNUMode();
9882         return 1;
9883 }
9884
9885 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9886 {
9887     char buf[MSG_SIZ], *p, *q;
9888     int i=1;
9889     while(*names) {
9890         p = names; q = buf;
9891         while(*p && *p != '\n') *q++ = *p++;
9892         *q = 0;
9893         if(engineList[i]) free(engineList[i]);
9894         engineList[i] = strdup(buf);
9895         if(*p == '\n') p++;
9896         TidyProgramName(engineList[i], "localhost", buf);
9897         if(engineMnemonic[i]) free(engineMnemonic[i]);
9898         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9899             strcat(buf, " (");
9900             sscanf(q + 8, "%s", buf + strlen(buf));
9901             strcat(buf, ")");
9902         }
9903         engineMnemonic[i] = strdup(buf);
9904         names = p; i++;
9905       if(i > MAXENGINES - 2) break;
9906     }
9907     engineList[i] = engineMnemonic[i] = NULL;
9908 }
9909
9910 // following implemented as macro to avoid type limitations
9911 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9912
9913 void SwapEngines(int n)
9914 {   // swap settings for first engine and other engine (so far only some selected options)
9915     int h;
9916     char *p;
9917     if(n == 0) return;
9918     SWAP(directory, p)
9919     SWAP(chessProgram, p)
9920     SWAP(isUCI, h)
9921     SWAP(hasOwnBookUCI, h)
9922     SWAP(protocolVersion, h)
9923     SWAP(reuse, h)
9924     SWAP(scoreIsAbsolute, h)
9925     SWAP(timeOdds, h)
9926     SWAP(logo, p)
9927     SWAP(pgnName, p)
9928     SWAP(pvSAN, h)
9929     SWAP(engOptions, p)
9930 }
9931
9932 void
9933 SetPlayer(int player)
9934 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9935     int i;
9936     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9937     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9938     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9939     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9940     if(mnemonic[i]) {
9941         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9942         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9943         appData.firstHasOwnBookUCI = !appData.defNoBook;
9944         ParseArgsFromString(buf);
9945     }
9946     free(engineName);
9947 }
9948
9949 int
9950 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9951 {   // determine players from game number
9952     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9953
9954     if(appData.tourneyType == 0) {
9955         roundsPerCycle = (nPlayers - 1) | 1;
9956         pairingsPerRound = nPlayers / 2;
9957     } else if(appData.tourneyType > 0) {
9958         roundsPerCycle = nPlayers - appData.tourneyType;
9959         pairingsPerRound = appData.tourneyType;
9960     }
9961     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9962     gamesPerCycle = gamesPerRound * roundsPerCycle;
9963     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9964     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9965     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9966     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9967     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9968     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9969
9970     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9971     if(appData.roundSync) *syncInterval = gamesPerRound;
9972
9973     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9974
9975     if(appData.tourneyType == 0) {
9976         if(curPairing == (nPlayers-1)/2 ) {
9977             *whitePlayer = curRound;
9978             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9979         } else {
9980             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9981             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9982             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9983             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9984         }
9985     } else if(appData.tourneyType > 0) {
9986         *whitePlayer = curPairing;
9987         *blackPlayer = curRound + appData.tourneyType;
9988     }
9989
9990     // take care of white/black alternation per round. 
9991     // For cycles and games this is already taken care of by default, derived from matchGame!
9992     return curRound & 1;
9993 }
9994
9995 int
9996 NextTourneyGame(int nr, int *swapColors)
9997 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9998     char *p, *q;
9999     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10000     FILE *tf;
10001     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10002     tf = fopen(appData.tourneyFile, "r");
10003     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10004     ParseArgsFromFile(tf); fclose(tf);
10005     InitTimeControls(); // TC might be altered from tourney file
10006
10007     nPlayers = CountPlayers(appData.participants); // count participants
10008     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10009     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10010
10011     if(syncInterval) {
10012         p = q = appData.results;
10013         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10014         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10015             DisplayMessage(_("Waiting for other game(s)"),"");
10016             waitingForGame = TRUE;
10017             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10018             return 0;
10019         }
10020         waitingForGame = FALSE;
10021     }
10022
10023     if(appData.tourneyType < 0) {
10024         if(nr>=0 && !pairingReceived) {
10025             char buf[1<<16];
10026             if(pairing.pr == NoProc) {
10027                 if(!appData.pairingEngine[0]) {
10028                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10029                     return 0;
10030                 }
10031                 StartChessProgram(&pairing); // starts the pairing engine
10032             }
10033             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10034             SendToProgram(buf, &pairing);
10035             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10036             SendToProgram(buf, &pairing);
10037             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10038         }
10039         pairingReceived = 0;                              // ... so we continue here 
10040         *swapColors = 0;
10041         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10042         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10043         matchGame = 1; roundNr = nr / syncInterval + 1;
10044     }
10045
10046     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10047
10048     // redefine engines, engine dir, etc.
10049     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10050     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10051     SwapEngines(1);
10052     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10053     SwapEngines(1);         // and make that valid for second engine by swapping
10054     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10055     InitEngine(&second, 1);
10056     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10057     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10058     return 1;
10059 }
10060
10061 void
10062 NextMatchGame()
10063 {   // performs game initialization that does not invoke engines, and then tries to start the game
10064     int res, firstWhite, swapColors = 0;
10065     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10066     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10067     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10068     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10069     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10070     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10071     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10072     Reset(FALSE, first.pr != NoProc);
10073     res = LoadGameOrPosition(matchGame); // setup game
10074     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10075     if(!res) return; // abort when bad game/pos file
10076     TwoMachinesEvent();
10077 }
10078
10079 void UserAdjudicationEvent( int result )
10080 {
10081     ChessMove gameResult = GameIsDrawn;
10082
10083     if( result > 0 ) {
10084         gameResult = WhiteWins;
10085     }
10086     else if( result < 0 ) {
10087         gameResult = BlackWins;
10088     }
10089
10090     if( gameMode == TwoMachinesPlay ) {
10091         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10092     }
10093 }
10094
10095
10096 // [HGM] save: calculate checksum of game to make games easily identifiable
10097 int StringCheckSum(char *s)
10098 {
10099         int i = 0;
10100         if(s==NULL) return 0;
10101         while(*s) i = i*259 + *s++;
10102         return i;
10103 }
10104
10105 int GameCheckSum()
10106 {
10107         int i, sum=0;
10108         for(i=backwardMostMove; i<forwardMostMove; i++) {
10109                 sum += pvInfoList[i].depth;
10110                 sum += StringCheckSum(parseList[i]);
10111                 sum += StringCheckSum(commentList[i]);
10112                 sum *= 261;
10113         }
10114         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10115         return sum + StringCheckSum(commentList[i]);
10116 } // end of save patch
10117
10118 void
10119 GameEnds(result, resultDetails, whosays)
10120      ChessMove result;
10121      char *resultDetails;
10122      int whosays;
10123 {
10124     GameMode nextGameMode;
10125     int isIcsGame;
10126     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10127
10128     if(endingGame) return; /* [HGM] crash: forbid recursion */
10129     endingGame = 1;
10130     if(twoBoards) { // [HGM] dual: switch back to one board
10131         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10132         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10133     }
10134     if (appData.debugMode) {
10135       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10136               result, resultDetails ? resultDetails : "(null)", whosays);
10137     }
10138
10139     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10140
10141     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10142         /* If we are playing on ICS, the server decides when the
10143            game is over, but the engine can offer to draw, claim
10144            a draw, or resign.
10145          */
10146 #if ZIPPY
10147         if (appData.zippyPlay && first.initDone) {
10148             if (result == GameIsDrawn) {
10149                 /* In case draw still needs to be claimed */
10150                 SendToICS(ics_prefix);
10151                 SendToICS("draw\n");
10152             } else if (StrCaseStr(resultDetails, "resign")) {
10153                 SendToICS(ics_prefix);
10154                 SendToICS("resign\n");
10155             }
10156         }
10157 #endif
10158         endingGame = 0; /* [HGM] crash */
10159         return;
10160     }
10161
10162     /* If we're loading the game from a file, stop */
10163     if (whosays == GE_FILE) {
10164       (void) StopLoadGameTimer();
10165       gameFileFP = NULL;
10166     }
10167
10168     /* Cancel draw offers */
10169     first.offeredDraw = second.offeredDraw = 0;
10170
10171     /* If this is an ICS game, only ICS can really say it's done;
10172        if not, anyone can. */
10173     isIcsGame = (gameMode == IcsPlayingWhite ||
10174                  gameMode == IcsPlayingBlack ||
10175                  gameMode == IcsObserving    ||
10176                  gameMode == IcsExamining);
10177
10178     if (!isIcsGame || whosays == GE_ICS) {
10179         /* OK -- not an ICS game, or ICS said it was done */
10180         StopClocks();
10181         if (!isIcsGame && !appData.noChessProgram)
10182           SetUserThinkingEnables();
10183
10184         /* [HGM] if a machine claims the game end we verify this claim */
10185         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10186             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10187                 char claimer;
10188                 ChessMove trueResult = (ChessMove) -1;
10189
10190                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10191                                             first.twoMachinesColor[0] :
10192                                             second.twoMachinesColor[0] ;
10193
10194                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10195                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10196                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10197                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10198                 } else
10199                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10200                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10201                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10202                 } else
10203                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10204                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10205                 }
10206
10207                 // now verify win claims, but not in drop games, as we don't understand those yet
10208                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10209                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10210                     (result == WhiteWins && claimer == 'w' ||
10211                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10212                       if (appData.debugMode) {
10213                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10214                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10215                       }
10216                       if(result != trueResult) {
10217                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10218                               result = claimer == 'w' ? BlackWins : WhiteWins;
10219                               resultDetails = buf;
10220                       }
10221                 } else
10222                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10223                     && (forwardMostMove <= backwardMostMove ||
10224                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10225                         (claimer=='b')==(forwardMostMove&1))
10226                                                                                   ) {
10227                       /* [HGM] verify: draws that were not flagged are false claims */
10228                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10229                       result = claimer == 'w' ? BlackWins : WhiteWins;
10230                       resultDetails = buf;
10231                 }
10232                 /* (Claiming a loss is accepted no questions asked!) */
10233             }
10234             /* [HGM] bare: don't allow bare King to win */
10235             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10236                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10237                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10238                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10239                && result != GameIsDrawn)
10240             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10241                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10242                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10243                         if(p >= 0 && p <= (int)WhiteKing) k++;
10244                 }
10245                 if (appData.debugMode) {
10246                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10247                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10248                 }
10249                 if(k <= 1) {
10250                         result = GameIsDrawn;
10251                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10252                         resultDetails = buf;
10253                 }
10254             }
10255         }
10256
10257
10258         if(serverMoves != NULL && !loadFlag) { char c = '=';
10259             if(result==WhiteWins) c = '+';
10260             if(result==BlackWins) c = '-';
10261             if(resultDetails != NULL)
10262                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10263         }
10264         if (resultDetails != NULL) {
10265             gameInfo.result = result;
10266             gameInfo.resultDetails = StrSave(resultDetails);
10267
10268             /* display last move only if game was not loaded from file */
10269             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10270                 DisplayMove(currentMove - 1);
10271
10272             if (forwardMostMove != 0) {
10273                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10274                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10275                                                                 ) {
10276                     if (*appData.saveGameFile != NULLCHAR) {
10277                         SaveGameToFile(appData.saveGameFile, TRUE);
10278                     } else if (appData.autoSaveGames) {
10279                         AutoSaveGame();
10280                     }
10281                     if (*appData.savePositionFile != NULLCHAR) {
10282                         SavePositionToFile(appData.savePositionFile);
10283                     }
10284                 }
10285             }
10286
10287             /* Tell program how game ended in case it is learning */
10288             /* [HGM] Moved this to after saving the PGN, just in case */
10289             /* engine died and we got here through time loss. In that */
10290             /* case we will get a fatal error writing the pipe, which */
10291             /* would otherwise lose us the PGN.                       */
10292             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10293             /* output during GameEnds should never be fatal anymore   */
10294             if (gameMode == MachinePlaysWhite ||
10295                 gameMode == MachinePlaysBlack ||
10296                 gameMode == TwoMachinesPlay ||
10297                 gameMode == IcsPlayingWhite ||
10298                 gameMode == IcsPlayingBlack ||
10299                 gameMode == BeginningOfGame) {
10300                 char buf[MSG_SIZ];
10301                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10302                         resultDetails);
10303                 if (first.pr != NoProc) {
10304                     SendToProgram(buf, &first);
10305                 }
10306                 if (second.pr != NoProc &&
10307                     gameMode == TwoMachinesPlay) {
10308                     SendToProgram(buf, &second);
10309                 }
10310             }
10311         }
10312
10313         if (appData.icsActive) {
10314             if (appData.quietPlay &&
10315                 (gameMode == IcsPlayingWhite ||
10316                  gameMode == IcsPlayingBlack)) {
10317                 SendToICS(ics_prefix);
10318                 SendToICS("set shout 1\n");
10319             }
10320             nextGameMode = IcsIdle;
10321             ics_user_moved = FALSE;
10322             /* clean up premove.  It's ugly when the game has ended and the
10323              * premove highlights are still on the board.
10324              */
10325             if (gotPremove) {
10326               gotPremove = FALSE;
10327               ClearPremoveHighlights();
10328               DrawPosition(FALSE, boards[currentMove]);
10329             }
10330             if (whosays == GE_ICS) {
10331                 switch (result) {
10332                 case WhiteWins:
10333                     if (gameMode == IcsPlayingWhite)
10334                         PlayIcsWinSound();
10335                     else if(gameMode == IcsPlayingBlack)
10336                         PlayIcsLossSound();
10337                     break;
10338                 case BlackWins:
10339                     if (gameMode == IcsPlayingBlack)
10340                         PlayIcsWinSound();
10341                     else if(gameMode == IcsPlayingWhite)
10342                         PlayIcsLossSound();
10343                     break;
10344                 case GameIsDrawn:
10345                     PlayIcsDrawSound();
10346                     break;
10347                 default:
10348                     PlayIcsUnfinishedSound();
10349                 }
10350             }
10351         } else if (gameMode == EditGame ||
10352                    gameMode == PlayFromGameFile ||
10353                    gameMode == AnalyzeMode ||
10354                    gameMode == AnalyzeFile) {
10355             nextGameMode = gameMode;
10356         } else {
10357             nextGameMode = EndOfGame;
10358         }
10359         pausing = FALSE;
10360         ModeHighlight();
10361     } else {
10362         nextGameMode = gameMode;
10363     }
10364
10365     if (appData.noChessProgram) {
10366         gameMode = nextGameMode;
10367         ModeHighlight();
10368         endingGame = 0; /* [HGM] crash */
10369         return;
10370     }
10371
10372     if (first.reuse) {
10373         /* Put first chess program into idle state */
10374         if (first.pr != NoProc &&
10375             (gameMode == MachinePlaysWhite ||
10376              gameMode == MachinePlaysBlack ||
10377              gameMode == TwoMachinesPlay ||
10378              gameMode == IcsPlayingWhite ||
10379              gameMode == IcsPlayingBlack ||
10380              gameMode == BeginningOfGame)) {
10381             SendToProgram("force\n", &first);
10382             if (first.usePing) {
10383               char buf[MSG_SIZ];
10384               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10385               SendToProgram(buf, &first);
10386             }
10387         }
10388     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10389         /* Kill off first chess program */
10390         if (first.isr != NULL)
10391           RemoveInputSource(first.isr);
10392         first.isr = NULL;
10393
10394         if (first.pr != NoProc) {
10395             ExitAnalyzeMode();
10396             DoSleep( appData.delayBeforeQuit );
10397             SendToProgram("quit\n", &first);
10398             DoSleep( appData.delayAfterQuit );
10399             DestroyChildProcess(first.pr, first.useSigterm);
10400         }
10401         first.pr = NoProc;
10402     }
10403     if (second.reuse) {
10404         /* Put second chess program into idle state */
10405         if (second.pr != NoProc &&
10406             gameMode == TwoMachinesPlay) {
10407             SendToProgram("force\n", &second);
10408             if (second.usePing) {
10409               char buf[MSG_SIZ];
10410               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10411               SendToProgram(buf, &second);
10412             }
10413         }
10414     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10415         /* Kill off second chess program */
10416         if (second.isr != NULL)
10417           RemoveInputSource(second.isr);
10418         second.isr = NULL;
10419
10420         if (second.pr != NoProc) {
10421             DoSleep( appData.delayBeforeQuit );
10422             SendToProgram("quit\n", &second);
10423             DoSleep( appData.delayAfterQuit );
10424             DestroyChildProcess(second.pr, second.useSigterm);
10425         }
10426         second.pr = NoProc;
10427     }
10428
10429     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10430         char resChar = '=';
10431         switch (result) {
10432         case WhiteWins:
10433           resChar = '+';
10434           if (first.twoMachinesColor[0] == 'w') {
10435             first.matchWins++;
10436           } else {
10437             second.matchWins++;
10438           }
10439           break;
10440         case BlackWins:
10441           resChar = '-';
10442           if (first.twoMachinesColor[0] == 'b') {
10443             first.matchWins++;
10444           } else {
10445             second.matchWins++;
10446           }
10447           break;
10448         case GameUnfinished:
10449           resChar = ' ';
10450         default:
10451           break;
10452         }
10453
10454         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10455         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10456             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10457             ReserveGame(nextGame, resChar); // sets nextGame
10458             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10459             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10460         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10461
10462         if (nextGame <= appData.matchGames && !abortMatch) {
10463             gameMode = nextGameMode;
10464             matchGame = nextGame; // this will be overruled in tourney mode!
10465             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10466             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10467             endingGame = 0; /* [HGM] crash */
10468             return;
10469         } else {
10470             gameMode = nextGameMode;
10471             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10472                      first.tidy, second.tidy,
10473                      first.matchWins, second.matchWins,
10474                      appData.matchGames - (first.matchWins + second.matchWins));
10475             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10476             if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10477             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10478             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10479                 first.twoMachinesColor = "black\n";
10480                 second.twoMachinesColor = "white\n";
10481             } else {
10482                 first.twoMachinesColor = "white\n";
10483                 second.twoMachinesColor = "black\n";
10484             }
10485         }
10486     }
10487     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10488         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10489       ExitAnalyzeMode();
10490     gameMode = nextGameMode;
10491     ModeHighlight();
10492     endingGame = 0;  /* [HGM] crash */
10493     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10494         if(matchMode == TRUE) { // match through command line: exit with or without popup
10495             if(ranking) {
10496                 ToNrEvent(forwardMostMove);
10497                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10498                 else ExitEvent(0);
10499             } else DisplayFatalError(buf, 0, 0);
10500         } else { // match through menu; just stop, with or without popup
10501             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10502             ModeHighlight();
10503             if(ranking){
10504                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10505             } else DisplayNote(buf);
10506       }
10507       if(ranking) free(ranking);
10508     }
10509 }
10510
10511 /* Assumes program was just initialized (initString sent).
10512    Leaves program in force mode. */
10513 void
10514 FeedMovesToProgram(cps, upto)
10515      ChessProgramState *cps;
10516      int upto;
10517 {
10518     int i;
10519
10520     if (appData.debugMode)
10521       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10522               startedFromSetupPosition ? "position and " : "",
10523               backwardMostMove, upto, cps->which);
10524     if(currentlyInitializedVariant != gameInfo.variant) {
10525       char buf[MSG_SIZ];
10526         // [HGM] variantswitch: make engine aware of new variant
10527         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10528                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10529         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10530         SendToProgram(buf, cps);
10531         currentlyInitializedVariant = gameInfo.variant;
10532     }
10533     SendToProgram("force\n", cps);
10534     if (startedFromSetupPosition) {
10535         SendBoard(cps, backwardMostMove);
10536     if (appData.debugMode) {
10537         fprintf(debugFP, "feedMoves\n");
10538     }
10539     }
10540     for (i = backwardMostMove; i < upto; i++) {
10541         SendMoveToProgram(i, cps);
10542     }
10543 }
10544
10545
10546 int
10547 ResurrectChessProgram()
10548 {
10549      /* The chess program may have exited.
10550         If so, restart it and feed it all the moves made so far. */
10551     static int doInit = 0;
10552
10553     if (appData.noChessProgram) return 1;
10554
10555     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10556         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10557         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10558         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10559     } else {
10560         if (first.pr != NoProc) return 1;
10561         StartChessProgram(&first);
10562     }
10563     InitChessProgram(&first, FALSE);
10564     FeedMovesToProgram(&first, currentMove);
10565
10566     if (!first.sendTime) {
10567         /* can't tell gnuchess what its clock should read,
10568            so we bow to its notion. */
10569         ResetClocks();
10570         timeRemaining[0][currentMove] = whiteTimeRemaining;
10571         timeRemaining[1][currentMove] = blackTimeRemaining;
10572     }
10573
10574     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10575                 appData.icsEngineAnalyze) && first.analysisSupport) {
10576       SendToProgram("analyze\n", &first);
10577       first.analyzing = TRUE;
10578     }
10579     return 1;
10580 }
10581
10582 /*
10583  * Button procedures
10584  */
10585 void
10586 Reset(redraw, init)
10587      int redraw, init;
10588 {
10589     int i;
10590
10591     if (appData.debugMode) {
10592         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10593                 redraw, init, gameMode);
10594     }
10595     CleanupTail(); // [HGM] vari: delete any stored variations
10596     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10597     pausing = pauseExamInvalid = FALSE;
10598     startedFromSetupPosition = blackPlaysFirst = FALSE;
10599     firstMove = TRUE;
10600     whiteFlag = blackFlag = FALSE;
10601     userOfferedDraw = FALSE;
10602     hintRequested = bookRequested = FALSE;
10603     first.maybeThinking = FALSE;
10604     second.maybeThinking = FALSE;
10605     first.bookSuspend = FALSE; // [HGM] book
10606     second.bookSuspend = FALSE;
10607     thinkOutput[0] = NULLCHAR;
10608     lastHint[0] = NULLCHAR;
10609     ClearGameInfo(&gameInfo);
10610     gameInfo.variant = StringToVariant(appData.variant);
10611     ics_user_moved = ics_clock_paused = FALSE;
10612     ics_getting_history = H_FALSE;
10613     ics_gamenum = -1;
10614     white_holding[0] = black_holding[0] = NULLCHAR;
10615     ClearProgramStats();
10616     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10617
10618     ResetFrontEnd();
10619     ClearHighlights();
10620     flipView = appData.flipView;
10621     ClearPremoveHighlights();
10622     gotPremove = FALSE;
10623     alarmSounded = FALSE;
10624
10625     GameEnds(EndOfFile, NULL, GE_PLAYER);
10626     if(appData.serverMovesName != NULL) {
10627         /* [HGM] prepare to make moves file for broadcasting */
10628         clock_t t = clock();
10629         if(serverMoves != NULL) fclose(serverMoves);
10630         serverMoves = fopen(appData.serverMovesName, "r");
10631         if(serverMoves != NULL) {
10632             fclose(serverMoves);
10633             /* delay 15 sec before overwriting, so all clients can see end */
10634             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10635         }
10636         serverMoves = fopen(appData.serverMovesName, "w");
10637     }
10638
10639     ExitAnalyzeMode();
10640     gameMode = BeginningOfGame;
10641     ModeHighlight();
10642     if(appData.icsActive) gameInfo.variant = VariantNormal;
10643     currentMove = forwardMostMove = backwardMostMove = 0;
10644     InitPosition(redraw);
10645     for (i = 0; i < MAX_MOVES; i++) {
10646         if (commentList[i] != NULL) {
10647             free(commentList[i]);
10648             commentList[i] = NULL;
10649         }
10650     }
10651     ResetClocks();
10652     timeRemaining[0][0] = whiteTimeRemaining;
10653     timeRemaining[1][0] = blackTimeRemaining;
10654
10655     if (first.pr == NoProc) {
10656         StartChessProgram(&first);
10657     }
10658     if (init) {
10659             InitChessProgram(&first, startedFromSetupPosition);
10660     }
10661     DisplayTitle("");
10662     DisplayMessage("", "");
10663     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10664     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10665 }
10666
10667 void
10668 AutoPlayGameLoop()
10669 {
10670     for (;;) {
10671         if (!AutoPlayOneMove())
10672           return;
10673         if (matchMode || appData.timeDelay == 0)
10674           continue;
10675         if (appData.timeDelay < 0)
10676           return;
10677         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10678         break;
10679     }
10680 }
10681
10682
10683 int
10684 AutoPlayOneMove()
10685 {
10686     int fromX, fromY, toX, toY;
10687
10688     if (appData.debugMode) {
10689       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10690     }
10691
10692     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10693       return FALSE;
10694
10695     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10696       pvInfoList[currentMove].depth = programStats.depth;
10697       pvInfoList[currentMove].score = programStats.score;
10698       pvInfoList[currentMove].time  = 0;
10699       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10700     }
10701
10702     if (currentMove >= forwardMostMove) {
10703       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10704 //      gameMode = EndOfGame;
10705 //      ModeHighlight();
10706
10707       /* [AS] Clear current move marker at the end of a game */
10708       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10709
10710       return FALSE;
10711     }
10712
10713     toX = moveList[currentMove][2] - AAA;
10714     toY = moveList[currentMove][3] - ONE;
10715
10716     if (moveList[currentMove][1] == '@') {
10717         if (appData.highlightLastMove) {
10718             SetHighlights(-1, -1, toX, toY);
10719         }
10720     } else {
10721         fromX = moveList[currentMove][0] - AAA;
10722         fromY = moveList[currentMove][1] - ONE;
10723
10724         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10725
10726         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10727
10728         if (appData.highlightLastMove) {
10729             SetHighlights(fromX, fromY, toX, toY);
10730         }
10731     }
10732     DisplayMove(currentMove);
10733     SendMoveToProgram(currentMove++, &first);
10734     DisplayBothClocks();
10735     DrawPosition(FALSE, boards[currentMove]);
10736     // [HGM] PV info: always display, routine tests if empty
10737     DisplayComment(currentMove - 1, commentList[currentMove]);
10738     return TRUE;
10739 }
10740
10741
10742 int
10743 LoadGameOneMove(readAhead)
10744      ChessMove readAhead;
10745 {
10746     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10747     char promoChar = NULLCHAR;
10748     ChessMove moveType;
10749     char move[MSG_SIZ];
10750     char *p, *q;
10751
10752     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10753         gameMode != AnalyzeMode && gameMode != Training) {
10754         gameFileFP = NULL;
10755         return FALSE;
10756     }
10757
10758     yyboardindex = forwardMostMove;
10759     if (readAhead != EndOfFile) {
10760       moveType = readAhead;
10761     } else {
10762       if (gameFileFP == NULL)
10763           return FALSE;
10764       moveType = (ChessMove) Myylex();
10765     }
10766
10767     done = FALSE;
10768     switch (moveType) {
10769       case Comment:
10770         if (appData.debugMode)
10771           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10772         p = yy_text;
10773
10774         /* append the comment but don't display it */
10775         AppendComment(currentMove, p, FALSE);
10776         return TRUE;
10777
10778       case WhiteCapturesEnPassant:
10779       case BlackCapturesEnPassant:
10780       case WhitePromotion:
10781       case BlackPromotion:
10782       case WhiteNonPromotion:
10783       case BlackNonPromotion:
10784       case NormalMove:
10785       case WhiteKingSideCastle:
10786       case WhiteQueenSideCastle:
10787       case BlackKingSideCastle:
10788       case BlackQueenSideCastle:
10789       case WhiteKingSideCastleWild:
10790       case WhiteQueenSideCastleWild:
10791       case BlackKingSideCastleWild:
10792       case BlackQueenSideCastleWild:
10793       /* PUSH Fabien */
10794       case WhiteHSideCastleFR:
10795       case WhiteASideCastleFR:
10796       case BlackHSideCastleFR:
10797       case BlackASideCastleFR:
10798       /* POP Fabien */
10799         if (appData.debugMode)
10800           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10801         fromX = currentMoveString[0] - AAA;
10802         fromY = currentMoveString[1] - ONE;
10803         toX = currentMoveString[2] - AAA;
10804         toY = currentMoveString[3] - ONE;
10805         promoChar = currentMoveString[4];
10806         break;
10807
10808       case WhiteDrop:
10809       case BlackDrop:
10810         if (appData.debugMode)
10811           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10812         fromX = moveType == WhiteDrop ?
10813           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10814         (int) CharToPiece(ToLower(currentMoveString[0]));
10815         fromY = DROP_RANK;
10816         toX = currentMoveString[2] - AAA;
10817         toY = currentMoveString[3] - ONE;
10818         break;
10819
10820       case WhiteWins:
10821       case BlackWins:
10822       case GameIsDrawn:
10823       case GameUnfinished:
10824         if (appData.debugMode)
10825           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10826         p = strchr(yy_text, '{');
10827         if (p == NULL) p = strchr(yy_text, '(');
10828         if (p == NULL) {
10829             p = yy_text;
10830             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10831         } else {
10832             q = strchr(p, *p == '{' ? '}' : ')');
10833             if (q != NULL) *q = NULLCHAR;
10834             p++;
10835         }
10836         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10837         GameEnds(moveType, p, GE_FILE);
10838         done = TRUE;
10839         if (cmailMsgLoaded) {
10840             ClearHighlights();
10841             flipView = WhiteOnMove(currentMove);
10842             if (moveType == GameUnfinished) flipView = !flipView;
10843             if (appData.debugMode)
10844               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10845         }
10846         break;
10847
10848       case EndOfFile:
10849         if (appData.debugMode)
10850           fprintf(debugFP, "Parser hit end of file\n");
10851         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10852           case MT_NONE:
10853           case MT_CHECK:
10854             break;
10855           case MT_CHECKMATE:
10856           case MT_STAINMATE:
10857             if (WhiteOnMove(currentMove)) {
10858                 GameEnds(BlackWins, "Black mates", GE_FILE);
10859             } else {
10860                 GameEnds(WhiteWins, "White mates", GE_FILE);
10861             }
10862             break;
10863           case MT_STALEMATE:
10864             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10865             break;
10866         }
10867         done = TRUE;
10868         break;
10869
10870       case MoveNumberOne:
10871         if (lastLoadGameStart == GNUChessGame) {
10872             /* GNUChessGames have numbers, but they aren't move numbers */
10873             if (appData.debugMode)
10874               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10875                       yy_text, (int) moveType);
10876             return LoadGameOneMove(EndOfFile); /* tail recursion */
10877         }
10878         /* else fall thru */
10879
10880       case XBoardGame:
10881       case GNUChessGame:
10882       case PGNTag:
10883         /* Reached start of next game in file */
10884         if (appData.debugMode)
10885           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10886         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10887           case MT_NONE:
10888           case MT_CHECK:
10889             break;
10890           case MT_CHECKMATE:
10891           case MT_STAINMATE:
10892             if (WhiteOnMove(currentMove)) {
10893                 GameEnds(BlackWins, "Black mates", GE_FILE);
10894             } else {
10895                 GameEnds(WhiteWins, "White mates", GE_FILE);
10896             }
10897             break;
10898           case MT_STALEMATE:
10899             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10900             break;
10901         }
10902         done = TRUE;
10903         break;
10904
10905       case PositionDiagram:     /* should not happen; ignore */
10906       case ElapsedTime:         /* ignore */
10907       case NAG:                 /* ignore */
10908         if (appData.debugMode)
10909           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10910                   yy_text, (int) moveType);
10911         return LoadGameOneMove(EndOfFile); /* tail recursion */
10912
10913       case IllegalMove:
10914         if (appData.testLegality) {
10915             if (appData.debugMode)
10916               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10917             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10918                     (forwardMostMove / 2) + 1,
10919                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10920             DisplayError(move, 0);
10921             done = TRUE;
10922         } else {
10923             if (appData.debugMode)
10924               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10925                       yy_text, currentMoveString);
10926             fromX = currentMoveString[0] - AAA;
10927             fromY = currentMoveString[1] - ONE;
10928             toX = currentMoveString[2] - AAA;
10929             toY = currentMoveString[3] - ONE;
10930             promoChar = currentMoveString[4];
10931         }
10932         break;
10933
10934       case AmbiguousMove:
10935         if (appData.debugMode)
10936           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10937         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10938                 (forwardMostMove / 2) + 1,
10939                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10940         DisplayError(move, 0);
10941         done = TRUE;
10942         break;
10943
10944       default:
10945       case ImpossibleMove:
10946         if (appData.debugMode)
10947           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10948         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10949                 (forwardMostMove / 2) + 1,
10950                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10951         DisplayError(move, 0);
10952         done = TRUE;
10953         break;
10954     }
10955
10956     if (done) {
10957         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10958             DrawPosition(FALSE, boards[currentMove]);
10959             DisplayBothClocks();
10960             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10961               DisplayComment(currentMove - 1, commentList[currentMove]);
10962         }
10963         (void) StopLoadGameTimer();
10964         gameFileFP = NULL;
10965         cmailOldMove = forwardMostMove;
10966         return FALSE;
10967     } else {
10968         /* currentMoveString is set as a side-effect of yylex */
10969
10970         thinkOutput[0] = NULLCHAR;
10971         MakeMove(fromX, fromY, toX, toY, promoChar);
10972         currentMove = forwardMostMove;
10973         return TRUE;
10974     }
10975 }
10976
10977 /* Load the nth game from the given file */
10978 int
10979 LoadGameFromFile(filename, n, title, useList)
10980      char *filename;
10981      int n;
10982      char *title;
10983      /*Boolean*/ int useList;
10984 {
10985     FILE *f;
10986     char buf[MSG_SIZ];
10987
10988     if (strcmp(filename, "-") == 0) {
10989         f = stdin;
10990         title = "stdin";
10991     } else {
10992         f = fopen(filename, "rb");
10993         if (f == NULL) {
10994           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10995             DisplayError(buf, errno);
10996             return FALSE;
10997         }
10998     }
10999     if (fseek(f, 0, 0) == -1) {
11000         /* f is not seekable; probably a pipe */
11001         useList = FALSE;
11002     }
11003     if (useList && n == 0) {
11004         int error = GameListBuild(f);
11005         if (error) {
11006             DisplayError(_("Cannot build game list"), error);
11007         } else if (!ListEmpty(&gameList) &&
11008                    ((ListGame *) gameList.tailPred)->number > 1) {
11009             GameListPopUp(f, title);
11010             return TRUE;
11011         }
11012         GameListDestroy();
11013         n = 1;
11014     }
11015     if (n == 0) n = 1;
11016     return LoadGame(f, n, title, FALSE);
11017 }
11018
11019
11020 void
11021 MakeRegisteredMove()
11022 {
11023     int fromX, fromY, toX, toY;
11024     char promoChar;
11025     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11026         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11027           case CMAIL_MOVE:
11028           case CMAIL_DRAW:
11029             if (appData.debugMode)
11030               fprintf(debugFP, "Restoring %s for game %d\n",
11031                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11032
11033             thinkOutput[0] = NULLCHAR;
11034             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11035             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11036             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11037             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11038             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11039             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11040             MakeMove(fromX, fromY, toX, toY, promoChar);
11041             ShowMove(fromX, fromY, toX, toY);
11042
11043             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11044               case MT_NONE:
11045               case MT_CHECK:
11046                 break;
11047
11048               case MT_CHECKMATE:
11049               case MT_STAINMATE:
11050                 if (WhiteOnMove(currentMove)) {
11051                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11052                 } else {
11053                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11054                 }
11055                 break;
11056
11057               case MT_STALEMATE:
11058                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11059                 break;
11060             }
11061
11062             break;
11063
11064           case CMAIL_RESIGN:
11065             if (WhiteOnMove(currentMove)) {
11066                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11067             } else {
11068                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11069             }
11070             break;
11071
11072           case CMAIL_ACCEPT:
11073             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11074             break;
11075
11076           default:
11077             break;
11078         }
11079     }
11080
11081     return;
11082 }
11083
11084 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11085 int
11086 CmailLoadGame(f, gameNumber, title, useList)
11087      FILE *f;
11088      int gameNumber;
11089      char *title;
11090      int useList;
11091 {
11092     int retVal;
11093
11094     if (gameNumber > nCmailGames) {
11095         DisplayError(_("No more games in this message"), 0);
11096         return FALSE;
11097     }
11098     if (f == lastLoadGameFP) {
11099         int offset = gameNumber - lastLoadGameNumber;
11100         if (offset == 0) {
11101             cmailMsg[0] = NULLCHAR;
11102             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11103                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11104                 nCmailMovesRegistered--;
11105             }
11106             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11107             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11108                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11109             }
11110         } else {
11111             if (! RegisterMove()) return FALSE;
11112         }
11113     }
11114
11115     retVal = LoadGame(f, gameNumber, title, useList);
11116
11117     /* Make move registered during previous look at this game, if any */
11118     MakeRegisteredMove();
11119
11120     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11121         commentList[currentMove]
11122           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11123         DisplayComment(currentMove - 1, commentList[currentMove]);
11124     }
11125
11126     return retVal;
11127 }
11128
11129 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11130 int
11131 ReloadGame(offset)
11132      int offset;
11133 {
11134     int gameNumber = lastLoadGameNumber + offset;
11135     if (lastLoadGameFP == NULL) {
11136         DisplayError(_("No game has been loaded yet"), 0);
11137         return FALSE;
11138     }
11139     if (gameNumber <= 0) {
11140         DisplayError(_("Can't back up any further"), 0);
11141         return FALSE;
11142     }
11143     if (cmailMsgLoaded) {
11144         return CmailLoadGame(lastLoadGameFP, gameNumber,
11145                              lastLoadGameTitle, lastLoadGameUseList);
11146     } else {
11147         return LoadGame(lastLoadGameFP, gameNumber,
11148                         lastLoadGameTitle, lastLoadGameUseList);
11149     }
11150 }
11151
11152 int keys[EmptySquare+1];
11153
11154 int
11155 PositionMatches(Board b1, Board b2)
11156 {
11157     int r, f, sum=0;
11158     switch(appData.searchMode) {
11159         case 1: return CompareWithRights(b1, b2);
11160         case 2:
11161             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11162                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11163             }
11164             return TRUE;
11165         case 3:
11166             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11167               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11168                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11169             }
11170             return sum==0;
11171         case 4:
11172             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11173                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11174             }
11175             return sum==0;
11176     }
11177     return TRUE;
11178 }
11179
11180 #define Q_PROMO  4
11181 #define Q_EP     3
11182 #define Q_BCASTL 2
11183 #define Q_WCASTL 1
11184
11185 int pieceList[256], quickBoard[256];
11186 ChessSquare pieceType[256] = { EmptySquare };
11187 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11188 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11189 int soughtTotal, turn;
11190 Boolean epOK, flipSearch;
11191
11192 typedef struct {
11193     unsigned char piece, to;
11194 } Move;
11195
11196 #define DSIZE (250000)
11197
11198 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11199 Move *moveDatabase = initialSpace;
11200 unsigned int movePtr, dataSize = DSIZE;
11201
11202 int MakePieceList(Board board, int *counts)
11203 {
11204     int r, f, n=Q_PROMO, total=0;
11205     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11206     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11207         int sq = f + (r<<4);
11208         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11209             quickBoard[sq] = ++n;
11210             pieceList[n] = sq;
11211             pieceType[n] = board[r][f];
11212             counts[board[r][f]]++;
11213             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11214             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11215             total++;
11216         }
11217     }
11218     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11219     return total;
11220 }
11221
11222 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11223 {
11224     int sq = fromX + (fromY<<4);
11225     int piece = quickBoard[sq];
11226     quickBoard[sq] = 0;
11227     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11228     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11229         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11230         moveDatabase[movePtr++].piece = Q_WCASTL;
11231         quickBoard[sq] = piece;
11232         piece = quickBoard[from]; quickBoard[from] = 0;
11233         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11234     } else
11235     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11236         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11237         moveDatabase[movePtr++].piece = Q_BCASTL;
11238         quickBoard[sq] = piece;
11239         piece = quickBoard[from]; quickBoard[from] = 0;
11240         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11241     } else
11242     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11243         quickBoard[(fromY<<4)+toX] = 0;
11244         moveDatabase[movePtr].piece = Q_EP;
11245         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11246         moveDatabase[movePtr].to = sq;
11247     } else
11248     if(promoPiece != pieceType[piece]) {
11249         moveDatabase[movePtr++].piece = Q_PROMO;
11250         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11251     }
11252     moveDatabase[movePtr].piece = piece;
11253     quickBoard[sq] = piece;
11254     movePtr++;
11255 }
11256
11257 int PackGame(Board board)
11258 {
11259     Move *newSpace = NULL;
11260     moveDatabase[movePtr].piece = 0; // terminate previous game
11261     if(movePtr > dataSize) {
11262         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11263         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11264         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11265         if(newSpace) {
11266             int i;
11267             Move *p = moveDatabase, *q = newSpace;
11268             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11269             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11270             moveDatabase = newSpace;
11271         } else { // calloc failed, we must be out of memory. Too bad...
11272             dataSize = 0; // prevent calloc events for all subsequent games
11273             return 0;     // and signal this one isn't cached
11274         }
11275     }
11276     movePtr++;
11277     MakePieceList(board, counts);
11278     return movePtr;
11279 }
11280
11281 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11282 {   // compare according to search mode
11283     int r, f;
11284     switch(appData.searchMode)
11285     {
11286       case 1: // exact position match
11287         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11288         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11289             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11290         }
11291         break;
11292       case 2: // can have extra material on empty squares
11293         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11294             if(board[r][f] == EmptySquare) continue;
11295             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11296         }
11297         break;
11298       case 3: // material with exact Pawn structure
11299         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11300             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11301             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11302         } // fall through to material comparison
11303       case 4: // exact material
11304         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11305         break;
11306       case 6: // material range with given imbalance
11307         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11308         // fall through to range comparison
11309       case 5: // material range
11310         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11311     }
11312     return TRUE;
11313 }
11314
11315 int QuickScan(Board board, Move *move)
11316 {   // reconstruct game,and compare all positions in it
11317     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11318     do {
11319         int piece = move->piece;
11320         int to = move->to, from = pieceList[piece];
11321         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11322           if(!piece) return -1;
11323           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11324             piece = (++move)->piece;
11325             from = pieceList[piece];
11326             counts[pieceType[piece]]--;
11327             pieceType[piece] = (ChessSquare) move->to;
11328             counts[move->to]++;
11329           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11330             counts[pieceType[quickBoard[to]]]--;
11331             quickBoard[to] = 0; total--;
11332             move++;
11333             continue;
11334           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11335             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11336             from  = pieceList[piece]; // so this must be King
11337             quickBoard[from] = 0;
11338             quickBoard[to] = piece;
11339             pieceList[piece] = to;
11340             move++;
11341             continue;
11342           }
11343         }
11344         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11345         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11346         quickBoard[from] = 0;
11347         quickBoard[to] = piece;
11348         pieceList[piece] = to;
11349         cnt++; turn ^= 3;
11350         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11351            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11352            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11353                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11354           ) {
11355             static int lastCounts[EmptySquare+1];
11356             int i;
11357             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11358             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11359         } else stretch = 0;
11360         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11361         move++;
11362     } while(1);
11363 }
11364
11365 void InitSearch()
11366 {
11367     int r, f;
11368     flipSearch = FALSE;
11369     CopyBoard(soughtBoard, boards[currentMove]);
11370     soughtTotal = MakePieceList(soughtBoard, maxSought);
11371     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11372     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11373     CopyBoard(reverseBoard, boards[currentMove]);
11374     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11375         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11376         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11377         reverseBoard[r][f] = piece;
11378     }
11379     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11380     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11381     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11382                  || (boards[currentMove][CASTLING][2] == NoRights || 
11383                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11384                  && (boards[currentMove][CASTLING][5] == NoRights || 
11385                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11386       ) {
11387         flipSearch = TRUE;
11388         CopyBoard(flipBoard, soughtBoard);
11389         CopyBoard(rotateBoard, reverseBoard);
11390         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11391             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11392             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11393         }
11394     }
11395     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11396     if(appData.searchMode >= 5) {
11397         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11398         MakePieceList(soughtBoard, minSought);
11399         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11400     }
11401     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11402         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11403 }
11404
11405 GameInfo dummyInfo;
11406
11407 int GameContainsPosition(FILE *f, ListGame *lg)
11408 {
11409     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11410     int fromX, fromY, toX, toY;
11411     char promoChar;
11412     static int initDone=FALSE;
11413
11414     // weed out games based on numerical tag comparison
11415     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11416     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11417     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11418     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11419     if(!initDone) {
11420         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11421         initDone = TRUE;
11422     }
11423     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11424     else CopyBoard(boards[scratch], initialPosition); // default start position
11425     if(lg->moves) {
11426         turn = btm + 1;
11427         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11428         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11429     }
11430     if(btm) plyNr++;
11431     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11432     fseek(f, lg->offset, 0);
11433     yynewfile(f);
11434     while(1) {
11435         yyboardindex = scratch;
11436         quickFlag = plyNr+1;
11437         next = Myylex();
11438         quickFlag = 0;
11439         switch(next) {
11440             case PGNTag:
11441                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11442             default:
11443                 continue;
11444
11445             case XBoardGame:
11446             case GNUChessGame:
11447                 if(plyNr) return -1; // after we have seen moves, this is for new game
11448               continue;
11449
11450             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11451             case ImpossibleMove:
11452             case WhiteWins: // game ends here with these four
11453             case BlackWins:
11454             case GameIsDrawn:
11455             case GameUnfinished:
11456                 return -1;
11457
11458             case IllegalMove:
11459                 if(appData.testLegality) return -1;
11460             case WhiteCapturesEnPassant:
11461             case BlackCapturesEnPassant:
11462             case WhitePromotion:
11463             case BlackPromotion:
11464             case WhiteNonPromotion:
11465             case BlackNonPromotion:
11466             case NormalMove:
11467             case WhiteKingSideCastle:
11468             case WhiteQueenSideCastle:
11469             case BlackKingSideCastle:
11470             case BlackQueenSideCastle:
11471             case WhiteKingSideCastleWild:
11472             case WhiteQueenSideCastleWild:
11473             case BlackKingSideCastleWild:
11474             case BlackQueenSideCastleWild:
11475             case WhiteHSideCastleFR:
11476             case WhiteASideCastleFR:
11477             case BlackHSideCastleFR:
11478             case BlackASideCastleFR:
11479                 fromX = currentMoveString[0] - AAA;
11480                 fromY = currentMoveString[1] - ONE;
11481                 toX = currentMoveString[2] - AAA;
11482                 toY = currentMoveString[3] - ONE;
11483                 promoChar = currentMoveString[4];
11484                 break;
11485             case WhiteDrop:
11486             case BlackDrop:
11487                 fromX = next == WhiteDrop ?
11488                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11489                   (int) CharToPiece(ToLower(currentMoveString[0]));
11490                 fromY = DROP_RANK;
11491                 toX = currentMoveString[2] - AAA;
11492                 toY = currentMoveString[3] - ONE;
11493                 promoChar = 0;
11494                 break;
11495         }
11496         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11497         plyNr++;
11498         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11499         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11500         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11501         if(appData.findMirror) {
11502             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11503             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11504         }
11505     }
11506 }
11507
11508 /* Load the nth game from open file f */
11509 int
11510 LoadGame(f, gameNumber, title, useList)
11511      FILE *f;
11512      int gameNumber;
11513      char *title;
11514      int useList;
11515 {
11516     ChessMove cm;
11517     char buf[MSG_SIZ];
11518     int gn = gameNumber;
11519     ListGame *lg = NULL;
11520     int numPGNTags = 0;
11521     int err, pos = -1;
11522     GameMode oldGameMode;
11523     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11524
11525     if (appData.debugMode)
11526         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11527
11528     if (gameMode == Training )
11529         SetTrainingModeOff();
11530
11531     oldGameMode = gameMode;
11532     if (gameMode != BeginningOfGame) {
11533       Reset(FALSE, TRUE);
11534     }
11535
11536     gameFileFP = f;
11537     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11538         fclose(lastLoadGameFP);
11539     }
11540
11541     if (useList) {
11542         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11543
11544         if (lg) {
11545             fseek(f, lg->offset, 0);
11546             GameListHighlight(gameNumber);
11547             pos = lg->position;
11548             gn = 1;
11549         }
11550         else {
11551             DisplayError(_("Game number out of range"), 0);
11552             return FALSE;
11553         }
11554     } else {
11555         GameListDestroy();
11556         if (fseek(f, 0, 0) == -1) {
11557             if (f == lastLoadGameFP ?
11558                 gameNumber == lastLoadGameNumber + 1 :
11559                 gameNumber == 1) {
11560                 gn = 1;
11561             } else {
11562                 DisplayError(_("Can't seek on game file"), 0);
11563                 return FALSE;
11564             }
11565         }
11566     }
11567     lastLoadGameFP = f;
11568     lastLoadGameNumber = gameNumber;
11569     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11570     lastLoadGameUseList = useList;
11571
11572     yynewfile(f);
11573
11574     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11575       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11576                 lg->gameInfo.black);
11577             DisplayTitle(buf);
11578     } else if (*title != NULLCHAR) {
11579         if (gameNumber > 1) {
11580           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11581             DisplayTitle(buf);
11582         } else {
11583             DisplayTitle(title);
11584         }
11585     }
11586
11587     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11588         gameMode = PlayFromGameFile;
11589         ModeHighlight();
11590     }
11591
11592     currentMove = forwardMostMove = backwardMostMove = 0;
11593     CopyBoard(boards[0], initialPosition);
11594     StopClocks();
11595
11596     /*
11597      * Skip the first gn-1 games in the file.
11598      * Also skip over anything that precedes an identifiable
11599      * start of game marker, to avoid being confused by
11600      * garbage at the start of the file.  Currently
11601      * recognized start of game markers are the move number "1",
11602      * the pattern "gnuchess .* game", the pattern
11603      * "^[#;%] [^ ]* game file", and a PGN tag block.
11604      * A game that starts with one of the latter two patterns
11605      * will also have a move number 1, possibly
11606      * following a position diagram.
11607      * 5-4-02: Let's try being more lenient and allowing a game to
11608      * start with an unnumbered move.  Does that break anything?
11609      */
11610     cm = lastLoadGameStart = EndOfFile;
11611     while (gn > 0) {
11612         yyboardindex = forwardMostMove;
11613         cm = (ChessMove) Myylex();
11614         switch (cm) {
11615           case EndOfFile:
11616             if (cmailMsgLoaded) {
11617                 nCmailGames = CMAIL_MAX_GAMES - gn;
11618             } else {
11619                 Reset(TRUE, TRUE);
11620                 DisplayError(_("Game not found in file"), 0);
11621             }
11622             return FALSE;
11623
11624           case GNUChessGame:
11625           case XBoardGame:
11626             gn--;
11627             lastLoadGameStart = cm;
11628             break;
11629
11630           case MoveNumberOne:
11631             switch (lastLoadGameStart) {
11632               case GNUChessGame:
11633               case XBoardGame:
11634               case PGNTag:
11635                 break;
11636               case MoveNumberOne:
11637               case EndOfFile:
11638                 gn--;           /* count this game */
11639                 lastLoadGameStart = cm;
11640                 break;
11641               default:
11642                 /* impossible */
11643                 break;
11644             }
11645             break;
11646
11647           case PGNTag:
11648             switch (lastLoadGameStart) {
11649               case GNUChessGame:
11650               case PGNTag:
11651               case MoveNumberOne:
11652               case EndOfFile:
11653                 gn--;           /* count this game */
11654                 lastLoadGameStart = cm;
11655                 break;
11656               case XBoardGame:
11657                 lastLoadGameStart = cm; /* game counted already */
11658                 break;
11659               default:
11660                 /* impossible */
11661                 break;
11662             }
11663             if (gn > 0) {
11664                 do {
11665                     yyboardindex = forwardMostMove;
11666                     cm = (ChessMove) Myylex();
11667                 } while (cm == PGNTag || cm == Comment);
11668             }
11669             break;
11670
11671           case WhiteWins:
11672           case BlackWins:
11673           case GameIsDrawn:
11674             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11675                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11676                     != CMAIL_OLD_RESULT) {
11677                     nCmailResults ++ ;
11678                     cmailResult[  CMAIL_MAX_GAMES
11679                                 - gn - 1] = CMAIL_OLD_RESULT;
11680                 }
11681             }
11682             break;
11683
11684           case NormalMove:
11685             /* Only a NormalMove can be at the start of a game
11686              * without a position diagram. */
11687             if (lastLoadGameStart == EndOfFile ) {
11688               gn--;
11689               lastLoadGameStart = MoveNumberOne;
11690             }
11691             break;
11692
11693           default:
11694             break;
11695         }
11696     }
11697
11698     if (appData.debugMode)
11699       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11700
11701     if (cm == XBoardGame) {
11702         /* Skip any header junk before position diagram and/or move 1 */
11703         for (;;) {
11704             yyboardindex = forwardMostMove;
11705             cm = (ChessMove) Myylex();
11706
11707             if (cm == EndOfFile ||
11708                 cm == GNUChessGame || cm == XBoardGame) {
11709                 /* Empty game; pretend end-of-file and handle later */
11710                 cm = EndOfFile;
11711                 break;
11712             }
11713
11714             if (cm == MoveNumberOne || cm == PositionDiagram ||
11715                 cm == PGNTag || cm == Comment)
11716               break;
11717         }
11718     } else if (cm == GNUChessGame) {
11719         if (gameInfo.event != NULL) {
11720             free(gameInfo.event);
11721         }
11722         gameInfo.event = StrSave(yy_text);
11723     }
11724
11725     startedFromSetupPosition = FALSE;
11726     while (cm == PGNTag) {
11727         if (appData.debugMode)
11728           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11729         err = ParsePGNTag(yy_text, &gameInfo);
11730         if (!err) numPGNTags++;
11731
11732         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11733         if(gameInfo.variant != oldVariant) {
11734             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11735             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11736             InitPosition(TRUE);
11737             oldVariant = gameInfo.variant;
11738             if (appData.debugMode)
11739               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11740         }
11741
11742
11743         if (gameInfo.fen != NULL) {
11744           Board initial_position;
11745           startedFromSetupPosition = TRUE;
11746           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11747             Reset(TRUE, TRUE);
11748             DisplayError(_("Bad FEN position in file"), 0);
11749             return FALSE;
11750           }
11751           CopyBoard(boards[0], initial_position);
11752           if (blackPlaysFirst) {
11753             currentMove = forwardMostMove = backwardMostMove = 1;
11754             CopyBoard(boards[1], initial_position);
11755             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11756             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11757             timeRemaining[0][1] = whiteTimeRemaining;
11758             timeRemaining[1][1] = blackTimeRemaining;
11759             if (commentList[0] != NULL) {
11760               commentList[1] = commentList[0];
11761               commentList[0] = NULL;
11762             }
11763           } else {
11764             currentMove = forwardMostMove = backwardMostMove = 0;
11765           }
11766           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11767           {   int i;
11768               initialRulePlies = FENrulePlies;
11769               for( i=0; i< nrCastlingRights; i++ )
11770                   initialRights[i] = initial_position[CASTLING][i];
11771           }
11772           yyboardindex = forwardMostMove;
11773           free(gameInfo.fen);
11774           gameInfo.fen = NULL;
11775         }
11776
11777         yyboardindex = forwardMostMove;
11778         cm = (ChessMove) Myylex();
11779
11780         /* Handle comments interspersed among the tags */
11781         while (cm == Comment) {
11782             char *p;
11783             if (appData.debugMode)
11784               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11785             p = yy_text;
11786             AppendComment(currentMove, p, FALSE);
11787             yyboardindex = forwardMostMove;
11788             cm = (ChessMove) Myylex();
11789         }
11790     }
11791
11792     /* don't rely on existence of Event tag since if game was
11793      * pasted from clipboard the Event tag may not exist
11794      */
11795     if (numPGNTags > 0){
11796         char *tags;
11797         if (gameInfo.variant == VariantNormal) {
11798           VariantClass v = StringToVariant(gameInfo.event);
11799           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11800           if(v < VariantShogi) gameInfo.variant = v;
11801         }
11802         if (!matchMode) {
11803           if( appData.autoDisplayTags ) {
11804             tags = PGNTags(&gameInfo);
11805             TagsPopUp(tags, CmailMsg());
11806             free(tags);
11807           }
11808         }
11809     } else {
11810         /* Make something up, but don't display it now */
11811         SetGameInfo();
11812         TagsPopDown();
11813     }
11814
11815     if (cm == PositionDiagram) {
11816         int i, j;
11817         char *p;
11818         Board initial_position;
11819
11820         if (appData.debugMode)
11821           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11822
11823         if (!startedFromSetupPosition) {
11824             p = yy_text;
11825             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11826               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11827                 switch (*p) {
11828                   case '{':
11829                   case '[':
11830                   case '-':
11831                   case ' ':
11832                   case '\t':
11833                   case '\n':
11834                   case '\r':
11835                     break;
11836                   default:
11837                     initial_position[i][j++] = CharToPiece(*p);
11838                     break;
11839                 }
11840             while (*p == ' ' || *p == '\t' ||
11841                    *p == '\n' || *p == '\r') p++;
11842
11843             if (strncmp(p, "black", strlen("black"))==0)
11844               blackPlaysFirst = TRUE;
11845             else
11846               blackPlaysFirst = FALSE;
11847             startedFromSetupPosition = TRUE;
11848
11849             CopyBoard(boards[0], initial_position);
11850             if (blackPlaysFirst) {
11851                 currentMove = forwardMostMove = backwardMostMove = 1;
11852                 CopyBoard(boards[1], initial_position);
11853                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11854                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11855                 timeRemaining[0][1] = whiteTimeRemaining;
11856                 timeRemaining[1][1] = blackTimeRemaining;
11857                 if (commentList[0] != NULL) {
11858                     commentList[1] = commentList[0];
11859                     commentList[0] = NULL;
11860                 }
11861             } else {
11862                 currentMove = forwardMostMove = backwardMostMove = 0;
11863             }
11864         }
11865         yyboardindex = forwardMostMove;
11866         cm = (ChessMove) Myylex();
11867     }
11868
11869     if (first.pr == NoProc) {
11870         StartChessProgram(&first);
11871     }
11872     InitChessProgram(&first, FALSE);
11873     SendToProgram("force\n", &first);
11874     if (startedFromSetupPosition) {
11875         SendBoard(&first, forwardMostMove);
11876     if (appData.debugMode) {
11877         fprintf(debugFP, "Load Game\n");
11878     }
11879         DisplayBothClocks();
11880     }
11881
11882     /* [HGM] server: flag to write setup moves in broadcast file as one */
11883     loadFlag = appData.suppressLoadMoves;
11884
11885     while (cm == Comment) {
11886         char *p;
11887         if (appData.debugMode)
11888           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11889         p = yy_text;
11890         AppendComment(currentMove, p, FALSE);
11891         yyboardindex = forwardMostMove;
11892         cm = (ChessMove) Myylex();
11893     }
11894
11895     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11896         cm == WhiteWins || cm == BlackWins ||
11897         cm == GameIsDrawn || cm == GameUnfinished) {
11898         DisplayMessage("", _("No moves in game"));
11899         if (cmailMsgLoaded) {
11900             if (appData.debugMode)
11901               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11902             ClearHighlights();
11903             flipView = FALSE;
11904         }
11905         DrawPosition(FALSE, boards[currentMove]);
11906         DisplayBothClocks();
11907         gameMode = EditGame;
11908         ModeHighlight();
11909         gameFileFP = NULL;
11910         cmailOldMove = 0;
11911         return TRUE;
11912     }
11913
11914     // [HGM] PV info: routine tests if comment empty
11915     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11916         DisplayComment(currentMove - 1, commentList[currentMove]);
11917     }
11918     if (!matchMode && appData.timeDelay != 0)
11919       DrawPosition(FALSE, boards[currentMove]);
11920
11921     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11922       programStats.ok_to_send = 1;
11923     }
11924
11925     /* if the first token after the PGN tags is a move
11926      * and not move number 1, retrieve it from the parser
11927      */
11928     if (cm != MoveNumberOne)
11929         LoadGameOneMove(cm);
11930
11931     /* load the remaining moves from the file */
11932     while (LoadGameOneMove(EndOfFile)) {
11933       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11934       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11935     }
11936
11937     /* rewind to the start of the game */
11938     currentMove = backwardMostMove;
11939
11940     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11941
11942     if (oldGameMode == AnalyzeFile ||
11943         oldGameMode == AnalyzeMode) {
11944       AnalyzeFileEvent();
11945     }
11946
11947     if (!matchMode && pos >= 0) {
11948         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11949     } else
11950     if (matchMode || appData.timeDelay == 0) {
11951       ToEndEvent();
11952     } else if (appData.timeDelay > 0) {
11953       AutoPlayGameLoop();
11954     }
11955
11956     if (appData.debugMode)
11957         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11958
11959     loadFlag = 0; /* [HGM] true game starts */
11960     return TRUE;
11961 }
11962
11963 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11964 int
11965 ReloadPosition(offset)
11966      int offset;
11967 {
11968     int positionNumber = lastLoadPositionNumber + offset;
11969     if (lastLoadPositionFP == NULL) {
11970         DisplayError(_("No position has been loaded yet"), 0);
11971         return FALSE;
11972     }
11973     if (positionNumber <= 0) {
11974         DisplayError(_("Can't back up any further"), 0);
11975         return FALSE;
11976     }
11977     return LoadPosition(lastLoadPositionFP, positionNumber,
11978                         lastLoadPositionTitle);
11979 }
11980
11981 /* Load the nth position from the given file */
11982 int
11983 LoadPositionFromFile(filename, n, title)
11984      char *filename;
11985      int n;
11986      char *title;
11987 {
11988     FILE *f;
11989     char buf[MSG_SIZ];
11990
11991     if (strcmp(filename, "-") == 0) {
11992         return LoadPosition(stdin, n, "stdin");
11993     } else {
11994         f = fopen(filename, "rb");
11995         if (f == NULL) {
11996             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11997             DisplayError(buf, errno);
11998             return FALSE;
11999         } else {
12000             return LoadPosition(f, n, title);
12001         }
12002     }
12003 }
12004
12005 /* Load the nth position from the given open file, and close it */
12006 int
12007 LoadPosition(f, positionNumber, title)
12008      FILE *f;
12009      int positionNumber;
12010      char *title;
12011 {
12012     char *p, line[MSG_SIZ];
12013     Board initial_position;
12014     int i, j, fenMode, pn;
12015
12016     if (gameMode == Training )
12017         SetTrainingModeOff();
12018
12019     if (gameMode != BeginningOfGame) {
12020         Reset(FALSE, TRUE);
12021     }
12022     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12023         fclose(lastLoadPositionFP);
12024     }
12025     if (positionNumber == 0) positionNumber = 1;
12026     lastLoadPositionFP = f;
12027     lastLoadPositionNumber = positionNumber;
12028     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12029     if (first.pr == NoProc && !appData.noChessProgram) {
12030       StartChessProgram(&first);
12031       InitChessProgram(&first, FALSE);
12032     }
12033     pn = positionNumber;
12034     if (positionNumber < 0) {
12035         /* Negative position number means to seek to that byte offset */
12036         if (fseek(f, -positionNumber, 0) == -1) {
12037             DisplayError(_("Can't seek on position file"), 0);
12038             return FALSE;
12039         };
12040         pn = 1;
12041     } else {
12042         if (fseek(f, 0, 0) == -1) {
12043             if (f == lastLoadPositionFP ?
12044                 positionNumber == lastLoadPositionNumber + 1 :
12045                 positionNumber == 1) {
12046                 pn = 1;
12047             } else {
12048                 DisplayError(_("Can't seek on position file"), 0);
12049                 return FALSE;
12050             }
12051         }
12052     }
12053     /* See if this file is FEN or old-style xboard */
12054     if (fgets(line, MSG_SIZ, f) == NULL) {
12055         DisplayError(_("Position not found in file"), 0);
12056         return FALSE;
12057     }
12058     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12059     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12060
12061     if (pn >= 2) {
12062         if (fenMode || line[0] == '#') pn--;
12063         while (pn > 0) {
12064             /* skip positions before number pn */
12065             if (fgets(line, MSG_SIZ, f) == NULL) {
12066                 Reset(TRUE, TRUE);
12067                 DisplayError(_("Position not found in file"), 0);
12068                 return FALSE;
12069             }
12070             if (fenMode || line[0] == '#') pn--;
12071         }
12072     }
12073
12074     if (fenMode) {
12075         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12076             DisplayError(_("Bad FEN position in file"), 0);
12077             return FALSE;
12078         }
12079     } else {
12080         (void) fgets(line, MSG_SIZ, f);
12081         (void) fgets(line, MSG_SIZ, f);
12082
12083         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12084             (void) fgets(line, MSG_SIZ, f);
12085             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12086                 if (*p == ' ')
12087                   continue;
12088                 initial_position[i][j++] = CharToPiece(*p);
12089             }
12090         }
12091
12092         blackPlaysFirst = FALSE;
12093         if (!feof(f)) {
12094             (void) fgets(line, MSG_SIZ, f);
12095             if (strncmp(line, "black", strlen("black"))==0)
12096               blackPlaysFirst = TRUE;
12097         }
12098     }
12099     startedFromSetupPosition = TRUE;
12100
12101     CopyBoard(boards[0], initial_position);
12102     if (blackPlaysFirst) {
12103         currentMove = forwardMostMove = backwardMostMove = 1;
12104         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12105         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12106         CopyBoard(boards[1], initial_position);
12107         DisplayMessage("", _("Black to play"));
12108     } else {
12109         currentMove = forwardMostMove = backwardMostMove = 0;
12110         DisplayMessage("", _("White to play"));
12111     }
12112     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12113     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12114         SendToProgram("force\n", &first);
12115         SendBoard(&first, forwardMostMove);
12116     }
12117     if (appData.debugMode) {
12118 int i, j;
12119   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12120   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12121         fprintf(debugFP, "Load Position\n");
12122     }
12123
12124     if (positionNumber > 1) {
12125       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12126         DisplayTitle(line);
12127     } else {
12128         DisplayTitle(title);
12129     }
12130     gameMode = EditGame;
12131     ModeHighlight();
12132     ResetClocks();
12133     timeRemaining[0][1] = whiteTimeRemaining;
12134     timeRemaining[1][1] = blackTimeRemaining;
12135     DrawPosition(FALSE, boards[currentMove]);
12136
12137     return TRUE;
12138 }
12139
12140
12141 void
12142 CopyPlayerNameIntoFileName(dest, src)
12143      char **dest, *src;
12144 {
12145     while (*src != NULLCHAR && *src != ',') {
12146         if (*src == ' ') {
12147             *(*dest)++ = '_';
12148             src++;
12149         } else {
12150             *(*dest)++ = *src++;
12151         }
12152     }
12153 }
12154
12155 char *DefaultFileName(ext)
12156      char *ext;
12157 {
12158     static char def[MSG_SIZ];
12159     char *p;
12160
12161     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12162         p = def;
12163         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12164         *p++ = '-';
12165         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12166         *p++ = '.';
12167         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12168     } else {
12169         def[0] = NULLCHAR;
12170     }
12171     return def;
12172 }
12173
12174 /* Save the current game to the given file */
12175 int
12176 SaveGameToFile(filename, append)
12177      char *filename;
12178      int append;
12179 {
12180     FILE *f;
12181     char buf[MSG_SIZ];
12182     int result, i, t,tot=0;
12183
12184     if (strcmp(filename, "-") == 0) {
12185         return SaveGame(stdout, 0, NULL);
12186     } else {
12187         for(i=0; i<10; i++) { // upto 10 tries
12188              f = fopen(filename, append ? "a" : "w");
12189              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12190              if(f || errno != 13) break;
12191              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12192              tot += t;
12193         }
12194         if (f == NULL) {
12195             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12196             DisplayError(buf, errno);
12197             return FALSE;
12198         } else {
12199             safeStrCpy(buf, lastMsg, MSG_SIZ);
12200             DisplayMessage(_("Waiting for access to save file"), "");
12201             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12202             DisplayMessage(_("Saving game"), "");
12203             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12204             result = SaveGame(f, 0, NULL);
12205             DisplayMessage(buf, "");
12206             return result;
12207         }
12208     }
12209 }
12210
12211 char *
12212 SavePart(str)
12213      char *str;
12214 {
12215     static char buf[MSG_SIZ];
12216     char *p;
12217
12218     p = strchr(str, ' ');
12219     if (p == NULL) return str;
12220     strncpy(buf, str, p - str);
12221     buf[p - str] = NULLCHAR;
12222     return buf;
12223 }
12224
12225 #define PGN_MAX_LINE 75
12226
12227 #define PGN_SIDE_WHITE  0
12228 #define PGN_SIDE_BLACK  1
12229
12230 /* [AS] */
12231 static int FindFirstMoveOutOfBook( int side )
12232 {
12233     int result = -1;
12234
12235     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12236         int index = backwardMostMove;
12237         int has_book_hit = 0;
12238
12239         if( (index % 2) != side ) {
12240             index++;
12241         }
12242
12243         while( index < forwardMostMove ) {
12244             /* Check to see if engine is in book */
12245             int depth = pvInfoList[index].depth;
12246             int score = pvInfoList[index].score;
12247             int in_book = 0;
12248
12249             if( depth <= 2 ) {
12250                 in_book = 1;
12251             }
12252             else if( score == 0 && depth == 63 ) {
12253                 in_book = 1; /* Zappa */
12254             }
12255             else if( score == 2 && depth == 99 ) {
12256                 in_book = 1; /* Abrok */
12257             }
12258
12259             has_book_hit += in_book;
12260
12261             if( ! in_book ) {
12262                 result = index;
12263
12264                 break;
12265             }
12266
12267             index += 2;
12268         }
12269     }
12270
12271     return result;
12272 }
12273
12274 /* [AS] */
12275 void GetOutOfBookInfo( char * buf )
12276 {
12277     int oob[2];
12278     int i;
12279     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12280
12281     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12282     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12283
12284     *buf = '\0';
12285
12286     if( oob[0] >= 0 || oob[1] >= 0 ) {
12287         for( i=0; i<2; i++ ) {
12288             int idx = oob[i];
12289
12290             if( idx >= 0 ) {
12291                 if( i > 0 && oob[0] >= 0 ) {
12292                     strcat( buf, "   " );
12293                 }
12294
12295                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12296                 sprintf( buf+strlen(buf), "%s%.2f",
12297                     pvInfoList[idx].score >= 0 ? "+" : "",
12298                     pvInfoList[idx].score / 100.0 );
12299             }
12300         }
12301     }
12302 }
12303
12304 /* Save game in PGN style and close the file */
12305 int
12306 SaveGamePGN(f)
12307      FILE *f;
12308 {
12309     int i, offset, linelen, newblock;
12310     time_t tm;
12311 //    char *movetext;
12312     char numtext[32];
12313     int movelen, numlen, blank;
12314     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12315
12316     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12317
12318     tm = time((time_t *) NULL);
12319
12320     PrintPGNTags(f, &gameInfo);
12321
12322     if (backwardMostMove > 0 || startedFromSetupPosition) {
12323         char *fen = PositionToFEN(backwardMostMove, NULL);
12324         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12325         fprintf(f, "\n{--------------\n");
12326         PrintPosition(f, backwardMostMove);
12327         fprintf(f, "--------------}\n");
12328         free(fen);
12329     }
12330     else {
12331         /* [AS] Out of book annotation */
12332         if( appData.saveOutOfBookInfo ) {
12333             char buf[64];
12334
12335             GetOutOfBookInfo( buf );
12336
12337             if( buf[0] != '\0' ) {
12338                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12339             }
12340         }
12341
12342         fprintf(f, "\n");
12343     }
12344
12345     i = backwardMostMove;
12346     linelen = 0;
12347     newblock = TRUE;
12348
12349     while (i < forwardMostMove) {
12350         /* Print comments preceding this move */
12351         if (commentList[i] != NULL) {
12352             if (linelen > 0) fprintf(f, "\n");
12353             fprintf(f, "%s", commentList[i]);
12354             linelen = 0;
12355             newblock = TRUE;
12356         }
12357
12358         /* Format move number */
12359         if ((i % 2) == 0)
12360           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12361         else
12362           if (newblock)
12363             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12364           else
12365             numtext[0] = NULLCHAR;
12366
12367         numlen = strlen(numtext);
12368         newblock = FALSE;
12369
12370         /* Print move number */
12371         blank = linelen > 0 && numlen > 0;
12372         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12373             fprintf(f, "\n");
12374             linelen = 0;
12375             blank = 0;
12376         }
12377         if (blank) {
12378             fprintf(f, " ");
12379             linelen++;
12380         }
12381         fprintf(f, "%s", numtext);
12382         linelen += numlen;
12383
12384         /* Get move */
12385         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12386         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12387
12388         /* Print move */
12389         blank = linelen > 0 && movelen > 0;
12390         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12391             fprintf(f, "\n");
12392             linelen = 0;
12393             blank = 0;
12394         }
12395         if (blank) {
12396             fprintf(f, " ");
12397             linelen++;
12398         }
12399         fprintf(f, "%s", move_buffer);
12400         linelen += movelen;
12401
12402         /* [AS] Add PV info if present */
12403         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12404             /* [HGM] add time */
12405             char buf[MSG_SIZ]; int seconds;
12406
12407             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12408
12409             if( seconds <= 0)
12410               buf[0] = 0;
12411             else
12412               if( seconds < 30 )
12413                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12414               else
12415                 {
12416                   seconds = (seconds + 4)/10; // round to full seconds
12417                   if( seconds < 60 )
12418                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12419                   else
12420                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12421                 }
12422
12423             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12424                       pvInfoList[i].score >= 0 ? "+" : "",
12425                       pvInfoList[i].score / 100.0,
12426                       pvInfoList[i].depth,
12427                       buf );
12428
12429             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12430
12431             /* Print score/depth */
12432             blank = linelen > 0 && movelen > 0;
12433             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12434                 fprintf(f, "\n");
12435                 linelen = 0;
12436                 blank = 0;
12437             }
12438             if (blank) {
12439                 fprintf(f, " ");
12440                 linelen++;
12441             }
12442             fprintf(f, "%s", move_buffer);
12443             linelen += movelen;
12444         }
12445
12446         i++;
12447     }
12448
12449     /* Start a new line */
12450     if (linelen > 0) fprintf(f, "\n");
12451
12452     /* Print comments after last move */
12453     if (commentList[i] != NULL) {
12454         fprintf(f, "%s\n", commentList[i]);
12455     }
12456
12457     /* Print result */
12458     if (gameInfo.resultDetails != NULL &&
12459         gameInfo.resultDetails[0] != NULLCHAR) {
12460         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12461                 PGNResult(gameInfo.result));
12462     } else {
12463         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12464     }
12465
12466     fclose(f);
12467     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12468     return TRUE;
12469 }
12470
12471 /* Save game in old style and close the file */
12472 int
12473 SaveGameOldStyle(f)
12474      FILE *f;
12475 {
12476     int i, offset;
12477     time_t tm;
12478
12479     tm = time((time_t *) NULL);
12480
12481     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12482     PrintOpponents(f);
12483
12484     if (backwardMostMove > 0 || startedFromSetupPosition) {
12485         fprintf(f, "\n[--------------\n");
12486         PrintPosition(f, backwardMostMove);
12487         fprintf(f, "--------------]\n");
12488     } else {
12489         fprintf(f, "\n");
12490     }
12491
12492     i = backwardMostMove;
12493     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12494
12495     while (i < forwardMostMove) {
12496         if (commentList[i] != NULL) {
12497             fprintf(f, "[%s]\n", commentList[i]);
12498         }
12499
12500         if ((i % 2) == 1) {
12501             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12502             i++;
12503         } else {
12504             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12505             i++;
12506             if (commentList[i] != NULL) {
12507                 fprintf(f, "\n");
12508                 continue;
12509             }
12510             if (i >= forwardMostMove) {
12511                 fprintf(f, "\n");
12512                 break;
12513             }
12514             fprintf(f, "%s\n", parseList[i]);
12515             i++;
12516         }
12517     }
12518
12519     if (commentList[i] != NULL) {
12520         fprintf(f, "[%s]\n", commentList[i]);
12521     }
12522
12523     /* This isn't really the old style, but it's close enough */
12524     if (gameInfo.resultDetails != NULL &&
12525         gameInfo.resultDetails[0] != NULLCHAR) {
12526         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12527                 gameInfo.resultDetails);
12528     } else {
12529         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12530     }
12531
12532     fclose(f);
12533     return TRUE;
12534 }
12535
12536 /* Save the current game to open file f and close the file */
12537 int
12538 SaveGame(f, dummy, dummy2)
12539      FILE *f;
12540      int dummy;
12541      char *dummy2;
12542 {
12543     if (gameMode == EditPosition) EditPositionDone(TRUE);
12544     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12545     if (appData.oldSaveStyle)
12546       return SaveGameOldStyle(f);
12547     else
12548       return SaveGamePGN(f);
12549 }
12550
12551 /* Save the current position to the given file */
12552 int
12553 SavePositionToFile(filename)
12554      char *filename;
12555 {
12556     FILE *f;
12557     char buf[MSG_SIZ];
12558
12559     if (strcmp(filename, "-") == 0) {
12560         return SavePosition(stdout, 0, NULL);
12561     } else {
12562         f = fopen(filename, "a");
12563         if (f == NULL) {
12564             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12565             DisplayError(buf, errno);
12566             return FALSE;
12567         } else {
12568             safeStrCpy(buf, lastMsg, MSG_SIZ);
12569             DisplayMessage(_("Waiting for access to save file"), "");
12570             flock(fileno(f), LOCK_EX); // [HGM] lock
12571             DisplayMessage(_("Saving position"), "");
12572             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12573             SavePosition(f, 0, NULL);
12574             DisplayMessage(buf, "");
12575             return TRUE;
12576         }
12577     }
12578 }
12579
12580 /* Save the current position to the given open file and close the file */
12581 int
12582 SavePosition(f, dummy, dummy2)
12583      FILE *f;
12584      int dummy;
12585      char *dummy2;
12586 {
12587     time_t tm;
12588     char *fen;
12589
12590     if (gameMode == EditPosition) EditPositionDone(TRUE);
12591     if (appData.oldSaveStyle) {
12592         tm = time((time_t *) NULL);
12593
12594         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12595         PrintOpponents(f);
12596         fprintf(f, "[--------------\n");
12597         PrintPosition(f, currentMove);
12598         fprintf(f, "--------------]\n");
12599     } else {
12600         fen = PositionToFEN(currentMove, NULL);
12601         fprintf(f, "%s\n", fen);
12602         free(fen);
12603     }
12604     fclose(f);
12605     return TRUE;
12606 }
12607
12608 void
12609 ReloadCmailMsgEvent(unregister)
12610      int unregister;
12611 {
12612 #if !WIN32
12613     static char *inFilename = NULL;
12614     static char *outFilename;
12615     int i;
12616     struct stat inbuf, outbuf;
12617     int status;
12618
12619     /* Any registered moves are unregistered if unregister is set, */
12620     /* i.e. invoked by the signal handler */
12621     if (unregister) {
12622         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12623             cmailMoveRegistered[i] = FALSE;
12624             if (cmailCommentList[i] != NULL) {
12625                 free(cmailCommentList[i]);
12626                 cmailCommentList[i] = NULL;
12627             }
12628         }
12629         nCmailMovesRegistered = 0;
12630     }
12631
12632     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12633         cmailResult[i] = CMAIL_NOT_RESULT;
12634     }
12635     nCmailResults = 0;
12636
12637     if (inFilename == NULL) {
12638         /* Because the filenames are static they only get malloced once  */
12639         /* and they never get freed                                      */
12640         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12641         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12642
12643         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12644         sprintf(outFilename, "%s.out", appData.cmailGameName);
12645     }
12646
12647     status = stat(outFilename, &outbuf);
12648     if (status < 0) {
12649         cmailMailedMove = FALSE;
12650     } else {
12651         status = stat(inFilename, &inbuf);
12652         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12653     }
12654
12655     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12656        counts the games, notes how each one terminated, etc.
12657
12658        It would be nice to remove this kludge and instead gather all
12659        the information while building the game list.  (And to keep it
12660        in the game list nodes instead of having a bunch of fixed-size
12661        parallel arrays.)  Note this will require getting each game's
12662        termination from the PGN tags, as the game list builder does
12663        not process the game moves.  --mann
12664        */
12665     cmailMsgLoaded = TRUE;
12666     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12667
12668     /* Load first game in the file or popup game menu */
12669     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12670
12671 #endif /* !WIN32 */
12672     return;
12673 }
12674
12675 int
12676 RegisterMove()
12677 {
12678     FILE *f;
12679     char string[MSG_SIZ];
12680
12681     if (   cmailMailedMove
12682         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12683         return TRUE;            /* Allow free viewing  */
12684     }
12685
12686     /* Unregister move to ensure that we don't leave RegisterMove        */
12687     /* with the move registered when the conditions for registering no   */
12688     /* longer hold                                                       */
12689     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12690         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12691         nCmailMovesRegistered --;
12692
12693         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12694           {
12695               free(cmailCommentList[lastLoadGameNumber - 1]);
12696               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12697           }
12698     }
12699
12700     if (cmailOldMove == -1) {
12701         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12702         return FALSE;
12703     }
12704
12705     if (currentMove > cmailOldMove + 1) {
12706         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12707         return FALSE;
12708     }
12709
12710     if (currentMove < cmailOldMove) {
12711         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12712         return FALSE;
12713     }
12714
12715     if (forwardMostMove > currentMove) {
12716         /* Silently truncate extra moves */
12717         TruncateGame();
12718     }
12719
12720     if (   (currentMove == cmailOldMove + 1)
12721         || (   (currentMove == cmailOldMove)
12722             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12723                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12724         if (gameInfo.result != GameUnfinished) {
12725             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12726         }
12727
12728         if (commentList[currentMove] != NULL) {
12729             cmailCommentList[lastLoadGameNumber - 1]
12730               = StrSave(commentList[currentMove]);
12731         }
12732         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12733
12734         if (appData.debugMode)
12735           fprintf(debugFP, "Saving %s for game %d\n",
12736                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12737
12738         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12739
12740         f = fopen(string, "w");
12741         if (appData.oldSaveStyle) {
12742             SaveGameOldStyle(f); /* also closes the file */
12743
12744             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12745             f = fopen(string, "w");
12746             SavePosition(f, 0, NULL); /* also closes the file */
12747         } else {
12748             fprintf(f, "{--------------\n");
12749             PrintPosition(f, currentMove);
12750             fprintf(f, "--------------}\n\n");
12751
12752             SaveGame(f, 0, NULL); /* also closes the file*/
12753         }
12754
12755         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12756         nCmailMovesRegistered ++;
12757     } else if (nCmailGames == 1) {
12758         DisplayError(_("You have not made a move yet"), 0);
12759         return FALSE;
12760     }
12761
12762     return TRUE;
12763 }
12764
12765 void
12766 MailMoveEvent()
12767 {
12768 #if !WIN32
12769     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12770     FILE *commandOutput;
12771     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12772     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12773     int nBuffers;
12774     int i;
12775     int archived;
12776     char *arcDir;
12777
12778     if (! cmailMsgLoaded) {
12779         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12780         return;
12781     }
12782
12783     if (nCmailGames == nCmailResults) {
12784         DisplayError(_("No unfinished games"), 0);
12785         return;
12786     }
12787
12788 #if CMAIL_PROHIBIT_REMAIL
12789     if (cmailMailedMove) {
12790       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);
12791         DisplayError(msg, 0);
12792         return;
12793     }
12794 #endif
12795
12796     if (! (cmailMailedMove || RegisterMove())) return;
12797
12798     if (   cmailMailedMove
12799         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12800       snprintf(string, MSG_SIZ, partCommandString,
12801                appData.debugMode ? " -v" : "", appData.cmailGameName);
12802         commandOutput = popen(string, "r");
12803
12804         if (commandOutput == NULL) {
12805             DisplayError(_("Failed to invoke cmail"), 0);
12806         } else {
12807             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12808                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12809             }
12810             if (nBuffers > 1) {
12811                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12812                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12813                 nBytes = MSG_SIZ - 1;
12814             } else {
12815                 (void) memcpy(msg, buffer, nBytes);
12816             }
12817             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12818
12819             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12820                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12821
12822                 archived = TRUE;
12823                 for (i = 0; i < nCmailGames; i ++) {
12824                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12825                         archived = FALSE;
12826                     }
12827                 }
12828                 if (   archived
12829                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12830                         != NULL)) {
12831                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12832                            arcDir,
12833                            appData.cmailGameName,
12834                            gameInfo.date);
12835                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12836                     cmailMsgLoaded = FALSE;
12837                 }
12838             }
12839
12840             DisplayInformation(msg);
12841             pclose(commandOutput);
12842         }
12843     } else {
12844         if ((*cmailMsg) != '\0') {
12845             DisplayInformation(cmailMsg);
12846         }
12847     }
12848
12849     return;
12850 #endif /* !WIN32 */
12851 }
12852
12853 char *
12854 CmailMsg()
12855 {
12856 #if WIN32
12857     return NULL;
12858 #else
12859     int  prependComma = 0;
12860     char number[5];
12861     char string[MSG_SIZ];       /* Space for game-list */
12862     int  i;
12863
12864     if (!cmailMsgLoaded) return "";
12865
12866     if (cmailMailedMove) {
12867       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12868     } else {
12869         /* Create a list of games left */
12870       snprintf(string, MSG_SIZ, "[");
12871         for (i = 0; i < nCmailGames; i ++) {
12872             if (! (   cmailMoveRegistered[i]
12873                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12874                 if (prependComma) {
12875                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12876                 } else {
12877                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12878                     prependComma = 1;
12879                 }
12880
12881                 strcat(string, number);
12882             }
12883         }
12884         strcat(string, "]");
12885
12886         if (nCmailMovesRegistered + nCmailResults == 0) {
12887             switch (nCmailGames) {
12888               case 1:
12889                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12890                 break;
12891
12892               case 2:
12893                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12894                 break;
12895
12896               default:
12897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12898                          nCmailGames);
12899                 break;
12900             }
12901         } else {
12902             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12903               case 1:
12904                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12905                          string);
12906                 break;
12907
12908               case 0:
12909                 if (nCmailResults == nCmailGames) {
12910                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12911                 } else {
12912                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12913                 }
12914                 break;
12915
12916               default:
12917                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12918                          string);
12919             }
12920         }
12921     }
12922     return cmailMsg;
12923 #endif /* WIN32 */
12924 }
12925
12926 void
12927 ResetGameEvent()
12928 {
12929     if (gameMode == Training)
12930       SetTrainingModeOff();
12931
12932     Reset(TRUE, TRUE);
12933     cmailMsgLoaded = FALSE;
12934     if (appData.icsActive) {
12935       SendToICS(ics_prefix);
12936       SendToICS("refresh\n");
12937     }
12938 }
12939
12940 void
12941 ExitEvent(status)
12942      int status;
12943 {
12944     exiting++;
12945     if (exiting > 2) {
12946       /* Give up on clean exit */
12947       exit(status);
12948     }
12949     if (exiting > 1) {
12950       /* Keep trying for clean exit */
12951       return;
12952     }
12953
12954     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12955
12956     if (telnetISR != NULL) {
12957       RemoveInputSource(telnetISR);
12958     }
12959     if (icsPR != NoProc) {
12960       DestroyChildProcess(icsPR, TRUE);
12961     }
12962
12963     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12964     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12965
12966     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12967     /* make sure this other one finishes before killing it!                  */
12968     if(endingGame) { int count = 0;
12969         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12970         while(endingGame && count++ < 10) DoSleep(1);
12971         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12972     }
12973
12974     /* Kill off chess programs */
12975     if (first.pr != NoProc) {
12976         ExitAnalyzeMode();
12977
12978         DoSleep( appData.delayBeforeQuit );
12979         SendToProgram("quit\n", &first);
12980         DoSleep( appData.delayAfterQuit );
12981         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12982     }
12983     if (second.pr != NoProc) {
12984         DoSleep( appData.delayBeforeQuit );
12985         SendToProgram("quit\n", &second);
12986         DoSleep( appData.delayAfterQuit );
12987         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12988     }
12989     if (first.isr != NULL) {
12990         RemoveInputSource(first.isr);
12991     }
12992     if (second.isr != NULL) {
12993         RemoveInputSource(second.isr);
12994     }
12995
12996     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12997     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12998
12999     ShutDownFrontEnd();
13000     exit(status);
13001 }
13002
13003 void
13004 PauseEvent()
13005 {
13006     if (appData.debugMode)
13007         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13008     if (pausing) {
13009         pausing = FALSE;
13010         ModeHighlight();
13011         if (gameMode == MachinePlaysWhite ||
13012             gameMode == MachinePlaysBlack) {
13013             StartClocks();
13014         } else {
13015             DisplayBothClocks();
13016         }
13017         if (gameMode == PlayFromGameFile) {
13018             if (appData.timeDelay >= 0)
13019                 AutoPlayGameLoop();
13020         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13021             Reset(FALSE, TRUE);
13022             SendToICS(ics_prefix);
13023             SendToICS("refresh\n");
13024         } else if (currentMove < forwardMostMove) {
13025             ForwardInner(forwardMostMove);
13026         }
13027         pauseExamInvalid = FALSE;
13028     } else {
13029         switch (gameMode) {
13030           default:
13031             return;
13032           case IcsExamining:
13033             pauseExamForwardMostMove = forwardMostMove;
13034             pauseExamInvalid = FALSE;
13035             /* fall through */
13036           case IcsObserving:
13037           case IcsPlayingWhite:
13038           case IcsPlayingBlack:
13039             pausing = TRUE;
13040             ModeHighlight();
13041             return;
13042           case PlayFromGameFile:
13043             (void) StopLoadGameTimer();
13044             pausing = TRUE;
13045             ModeHighlight();
13046             break;
13047           case BeginningOfGame:
13048             if (appData.icsActive) return;
13049             /* else fall through */
13050           case MachinePlaysWhite:
13051           case MachinePlaysBlack:
13052           case TwoMachinesPlay:
13053             if (forwardMostMove == 0)
13054               return;           /* don't pause if no one has moved */
13055             if ((gameMode == MachinePlaysWhite &&
13056                  !WhiteOnMove(forwardMostMove)) ||
13057                 (gameMode == MachinePlaysBlack &&
13058                  WhiteOnMove(forwardMostMove))) {
13059                 StopClocks();
13060             }
13061             pausing = TRUE;
13062             ModeHighlight();
13063             break;
13064         }
13065     }
13066 }
13067
13068 void
13069 EditCommentEvent()
13070 {
13071     char title[MSG_SIZ];
13072
13073     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13074       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13075     } else {
13076       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13077                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13078                parseList[currentMove - 1]);
13079     }
13080
13081     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13082 }
13083
13084
13085 void
13086 EditTagsEvent()
13087 {
13088     char *tags = PGNTags(&gameInfo);
13089     bookUp = FALSE;
13090     EditTagsPopUp(tags, NULL);
13091     free(tags);
13092 }
13093
13094 void
13095 AnalyzeModeEvent()
13096 {
13097     if (appData.noChessProgram || gameMode == AnalyzeMode)
13098       return;
13099
13100     if (gameMode != AnalyzeFile) {
13101         if (!appData.icsEngineAnalyze) {
13102                EditGameEvent();
13103                if (gameMode != EditGame) return;
13104         }
13105         ResurrectChessProgram();
13106         SendToProgram("analyze\n", &first);
13107         first.analyzing = TRUE;
13108         /*first.maybeThinking = TRUE;*/
13109         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13110         EngineOutputPopUp();
13111     }
13112     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13113     pausing = FALSE;
13114     ModeHighlight();
13115     SetGameInfo();
13116
13117     StartAnalysisClock();
13118     GetTimeMark(&lastNodeCountTime);
13119     lastNodeCount = 0;
13120 }
13121
13122 void
13123 AnalyzeFileEvent()
13124 {
13125     if (appData.noChessProgram || gameMode == AnalyzeFile)
13126       return;
13127
13128     if (gameMode != AnalyzeMode) {
13129         EditGameEvent();
13130         if (gameMode != EditGame) return;
13131         ResurrectChessProgram();
13132         SendToProgram("analyze\n", &first);
13133         first.analyzing = TRUE;
13134         /*first.maybeThinking = TRUE;*/
13135         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13136         EngineOutputPopUp();
13137     }
13138     gameMode = AnalyzeFile;
13139     pausing = FALSE;
13140     ModeHighlight();
13141     SetGameInfo();
13142
13143     StartAnalysisClock();
13144     GetTimeMark(&lastNodeCountTime);
13145     lastNodeCount = 0;
13146     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13147 }
13148
13149 void
13150 MachineWhiteEvent()
13151 {
13152     char buf[MSG_SIZ];
13153     char *bookHit = NULL;
13154
13155     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13156       return;
13157
13158
13159     if (gameMode == PlayFromGameFile ||
13160         gameMode == TwoMachinesPlay  ||
13161         gameMode == Training         ||
13162         gameMode == AnalyzeMode      ||
13163         gameMode == EndOfGame)
13164         EditGameEvent();
13165
13166     if (gameMode == EditPosition)
13167         EditPositionDone(TRUE);
13168
13169     if (!WhiteOnMove(currentMove)) {
13170         DisplayError(_("It is not White's turn"), 0);
13171         return;
13172     }
13173
13174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13175       ExitAnalyzeMode();
13176
13177     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13178         gameMode == AnalyzeFile)
13179         TruncateGame();
13180
13181     ResurrectChessProgram();    /* in case it isn't running */
13182     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13183         gameMode = MachinePlaysWhite;
13184         ResetClocks();
13185     } else
13186     gameMode = MachinePlaysWhite;
13187     pausing = FALSE;
13188     ModeHighlight();
13189     SetGameInfo();
13190     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13191     DisplayTitle(buf);
13192     if (first.sendName) {
13193       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13194       SendToProgram(buf, &first);
13195     }
13196     if (first.sendTime) {
13197       if (first.useColors) {
13198         SendToProgram("black\n", &first); /*gnu kludge*/
13199       }
13200       SendTimeRemaining(&first, TRUE);
13201     }
13202     if (first.useColors) {
13203       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13204     }
13205     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13206     SetMachineThinkingEnables();
13207     first.maybeThinking = TRUE;
13208     StartClocks();
13209     firstMove = FALSE;
13210
13211     if (appData.autoFlipView && !flipView) {
13212       flipView = !flipView;
13213       DrawPosition(FALSE, NULL);
13214       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13215     }
13216
13217     if(bookHit) { // [HGM] book: simulate book reply
13218         static char bookMove[MSG_SIZ]; // a bit generous?
13219
13220         programStats.nodes = programStats.depth = programStats.time =
13221         programStats.score = programStats.got_only_move = 0;
13222         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13223
13224         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13225         strcat(bookMove, bookHit);
13226         HandleMachineMove(bookMove, &first);
13227     }
13228 }
13229
13230 void
13231 MachineBlackEvent()
13232 {
13233   char buf[MSG_SIZ];
13234   char *bookHit = NULL;
13235
13236     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13237         return;
13238
13239
13240     if (gameMode == PlayFromGameFile ||
13241         gameMode == TwoMachinesPlay  ||
13242         gameMode == Training         ||
13243         gameMode == AnalyzeMode      ||
13244         gameMode == EndOfGame)
13245         EditGameEvent();
13246
13247     if (gameMode == EditPosition)
13248         EditPositionDone(TRUE);
13249
13250     if (WhiteOnMove(currentMove)) {
13251         DisplayError(_("It is not Black's turn"), 0);
13252         return;
13253     }
13254
13255     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13256       ExitAnalyzeMode();
13257
13258     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13259         gameMode == AnalyzeFile)
13260         TruncateGame();
13261
13262     ResurrectChessProgram();    /* in case it isn't running */
13263     gameMode = MachinePlaysBlack;
13264     pausing = FALSE;
13265     ModeHighlight();
13266     SetGameInfo();
13267     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13268     DisplayTitle(buf);
13269     if (first.sendName) {
13270       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13271       SendToProgram(buf, &first);
13272     }
13273     if (first.sendTime) {
13274       if (first.useColors) {
13275         SendToProgram("white\n", &first); /*gnu kludge*/
13276       }
13277       SendTimeRemaining(&first, FALSE);
13278     }
13279     if (first.useColors) {
13280       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13281     }
13282     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13283     SetMachineThinkingEnables();
13284     first.maybeThinking = TRUE;
13285     StartClocks();
13286
13287     if (appData.autoFlipView && flipView) {
13288       flipView = !flipView;
13289       DrawPosition(FALSE, NULL);
13290       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13291     }
13292     if(bookHit) { // [HGM] book: simulate book reply
13293         static char bookMove[MSG_SIZ]; // a bit generous?
13294
13295         programStats.nodes = programStats.depth = programStats.time =
13296         programStats.score = programStats.got_only_move = 0;
13297         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13298
13299         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13300         strcat(bookMove, bookHit);
13301         HandleMachineMove(bookMove, &first);
13302     }
13303 }
13304
13305
13306 void
13307 DisplayTwoMachinesTitle()
13308 {
13309     char buf[MSG_SIZ];
13310     if (appData.matchGames > 0) {
13311         if(appData.tourneyFile[0]) {
13312           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13313                    gameInfo.white, gameInfo.black,
13314                    nextGame+1, appData.matchGames+1,
13315                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13316         } else 
13317         if (first.twoMachinesColor[0] == 'w') {
13318           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13319                    gameInfo.white, gameInfo.black,
13320                    first.matchWins, second.matchWins,
13321                    matchGame - 1 - (first.matchWins + second.matchWins));
13322         } else {
13323           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13324                    gameInfo.white, gameInfo.black,
13325                    second.matchWins, first.matchWins,
13326                    matchGame - 1 - (first.matchWins + second.matchWins));
13327         }
13328     } else {
13329       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13330     }
13331     DisplayTitle(buf);
13332 }
13333
13334 void
13335 SettingsMenuIfReady()
13336 {
13337   if (second.lastPing != second.lastPong) {
13338     DisplayMessage("", _("Waiting for second chess program"));
13339     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13340     return;
13341   }
13342   ThawUI();
13343   DisplayMessage("", "");
13344   SettingsPopUp(&second);
13345 }
13346
13347 int
13348 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13349 {
13350     char buf[MSG_SIZ];
13351     if (cps->pr == NoProc) {
13352         StartChessProgram(cps);
13353         if (cps->protocolVersion == 1) {
13354           retry();
13355         } else {
13356           /* kludge: allow timeout for initial "feature" command */
13357           FreezeUI();
13358           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13359           DisplayMessage("", buf);
13360           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13361         }
13362         return 1;
13363     }
13364     return 0;
13365 }
13366
13367 void
13368 TwoMachinesEvent P((void))
13369 {
13370     int i;
13371     char buf[MSG_SIZ];
13372     ChessProgramState *onmove;
13373     char *bookHit = NULL;
13374     static int stalling = 0;
13375     TimeMark now;
13376     long wait;
13377
13378     if (appData.noChessProgram) return;
13379
13380     switch (gameMode) {
13381       case TwoMachinesPlay:
13382         return;
13383       case MachinePlaysWhite:
13384       case MachinePlaysBlack:
13385         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13386             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13387             return;
13388         }
13389         /* fall through */
13390       case BeginningOfGame:
13391       case PlayFromGameFile:
13392       case EndOfGame:
13393         EditGameEvent();
13394         if (gameMode != EditGame) return;
13395         break;
13396       case EditPosition:
13397         EditPositionDone(TRUE);
13398         break;
13399       case AnalyzeMode:
13400       case AnalyzeFile:
13401         ExitAnalyzeMode();
13402         break;
13403       case EditGame:
13404       default:
13405         break;
13406     }
13407
13408 //    forwardMostMove = currentMove;
13409     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13410
13411     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13412
13413     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13414     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13415       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13416       return;
13417     }
13418     if(!stalling) {
13419       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13420       SendToProgram("force\n", &second);
13421       stalling = 1;
13422       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13423       return;
13424     }
13425     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13426     if(appData.matchPause>10000 || appData.matchPause<10)
13427                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13428     wait = SubtractTimeMarks(&now, &pauseStart);
13429     if(wait < appData.matchPause) {
13430         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13431         return;
13432     }
13433     stalling = 0;
13434     DisplayMessage("", "");
13435     if (startedFromSetupPosition) {
13436         SendBoard(&second, backwardMostMove);
13437     if (appData.debugMode) {
13438         fprintf(debugFP, "Two Machines\n");
13439     }
13440     }
13441     for (i = backwardMostMove; i < forwardMostMove; i++) {
13442         SendMoveToProgram(i, &second);
13443     }
13444
13445     gameMode = TwoMachinesPlay;
13446     pausing = FALSE;
13447     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13448     SetGameInfo();
13449     DisplayTwoMachinesTitle();
13450     firstMove = TRUE;
13451     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13452         onmove = &first;
13453     } else {
13454         onmove = &second;
13455     }
13456     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13457     SendToProgram(first.computerString, &first);
13458     if (first.sendName) {
13459       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13460       SendToProgram(buf, &first);
13461     }
13462     SendToProgram(second.computerString, &second);
13463     if (second.sendName) {
13464       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13465       SendToProgram(buf, &second);
13466     }
13467
13468     ResetClocks();
13469     if (!first.sendTime || !second.sendTime) {
13470         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13471         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13472     }
13473     if (onmove->sendTime) {
13474       if (onmove->useColors) {
13475         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13476       }
13477       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13478     }
13479     if (onmove->useColors) {
13480       SendToProgram(onmove->twoMachinesColor, onmove);
13481     }
13482     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13483 //    SendToProgram("go\n", onmove);
13484     onmove->maybeThinking = TRUE;
13485     SetMachineThinkingEnables();
13486
13487     StartClocks();
13488
13489     if(bookHit) { // [HGM] book: simulate book reply
13490         static char bookMove[MSG_SIZ]; // a bit generous?
13491
13492         programStats.nodes = programStats.depth = programStats.time =
13493         programStats.score = programStats.got_only_move = 0;
13494         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13495
13496         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13497         strcat(bookMove, bookHit);
13498         savedMessage = bookMove; // args for deferred call
13499         savedState = onmove;
13500         ScheduleDelayedEvent(DeferredBookMove, 1);
13501     }
13502 }
13503
13504 void
13505 TrainingEvent()
13506 {
13507     if (gameMode == Training) {
13508       SetTrainingModeOff();
13509       gameMode = PlayFromGameFile;
13510       DisplayMessage("", _("Training mode off"));
13511     } else {
13512       gameMode = Training;
13513       animateTraining = appData.animate;
13514
13515       /* make sure we are not already at the end of the game */
13516       if (currentMove < forwardMostMove) {
13517         SetTrainingModeOn();
13518         DisplayMessage("", _("Training mode on"));
13519       } else {
13520         gameMode = PlayFromGameFile;
13521         DisplayError(_("Already at end of game"), 0);
13522       }
13523     }
13524     ModeHighlight();
13525 }
13526
13527 void
13528 IcsClientEvent()
13529 {
13530     if (!appData.icsActive) return;
13531     switch (gameMode) {
13532       case IcsPlayingWhite:
13533       case IcsPlayingBlack:
13534       case IcsObserving:
13535       case IcsIdle:
13536       case BeginningOfGame:
13537       case IcsExamining:
13538         return;
13539
13540       case EditGame:
13541         break;
13542
13543       case EditPosition:
13544         EditPositionDone(TRUE);
13545         break;
13546
13547       case AnalyzeMode:
13548       case AnalyzeFile:
13549         ExitAnalyzeMode();
13550         break;
13551
13552       default:
13553         EditGameEvent();
13554         break;
13555     }
13556
13557     gameMode = IcsIdle;
13558     ModeHighlight();
13559     return;
13560 }
13561
13562
13563 void
13564 EditGameEvent()
13565 {
13566     int i;
13567
13568     switch (gameMode) {
13569       case Training:
13570         SetTrainingModeOff();
13571         break;
13572       case MachinePlaysWhite:
13573       case MachinePlaysBlack:
13574       case BeginningOfGame:
13575         SendToProgram("force\n", &first);
13576         SetUserThinkingEnables();
13577         break;
13578       case PlayFromGameFile:
13579         (void) StopLoadGameTimer();
13580         if (gameFileFP != NULL) {
13581             gameFileFP = NULL;
13582         }
13583         break;
13584       case EditPosition:
13585         EditPositionDone(TRUE);
13586         break;
13587       case AnalyzeMode:
13588       case AnalyzeFile:
13589         ExitAnalyzeMode();
13590         SendToProgram("force\n", &first);
13591         break;
13592       case TwoMachinesPlay:
13593         GameEnds(EndOfFile, NULL, GE_PLAYER);
13594         ResurrectChessProgram();
13595         SetUserThinkingEnables();
13596         break;
13597       case EndOfGame:
13598         ResurrectChessProgram();
13599         break;
13600       case IcsPlayingBlack:
13601       case IcsPlayingWhite:
13602         DisplayError(_("Warning: You are still playing a game"), 0);
13603         break;
13604       case IcsObserving:
13605         DisplayError(_("Warning: You are still observing a game"), 0);
13606         break;
13607       case IcsExamining:
13608         DisplayError(_("Warning: You are still examining a game"), 0);
13609         break;
13610       case IcsIdle:
13611         break;
13612       case EditGame:
13613       default:
13614         return;
13615     }
13616
13617     pausing = FALSE;
13618     StopClocks();
13619     first.offeredDraw = second.offeredDraw = 0;
13620
13621     if (gameMode == PlayFromGameFile) {
13622         whiteTimeRemaining = timeRemaining[0][currentMove];
13623         blackTimeRemaining = timeRemaining[1][currentMove];
13624         DisplayTitle("");
13625     }
13626
13627     if (gameMode == MachinePlaysWhite ||
13628         gameMode == MachinePlaysBlack ||
13629         gameMode == TwoMachinesPlay ||
13630         gameMode == EndOfGame) {
13631         i = forwardMostMove;
13632         while (i > currentMove) {
13633             SendToProgram("undo\n", &first);
13634             i--;
13635         }
13636         if(!adjustedClock) {
13637         whiteTimeRemaining = timeRemaining[0][currentMove];
13638         blackTimeRemaining = timeRemaining[1][currentMove];
13639         DisplayBothClocks();
13640         }
13641         if (whiteFlag || blackFlag) {
13642             whiteFlag = blackFlag = 0;
13643         }
13644         DisplayTitle("");
13645     }
13646
13647     gameMode = EditGame;
13648     ModeHighlight();
13649     SetGameInfo();
13650 }
13651
13652
13653 void
13654 EditPositionEvent()
13655 {
13656     if (gameMode == EditPosition) {
13657         EditGameEvent();
13658         return;
13659     }
13660
13661     EditGameEvent();
13662     if (gameMode != EditGame) return;
13663
13664     gameMode = EditPosition;
13665     ModeHighlight();
13666     SetGameInfo();
13667     if (currentMove > 0)
13668       CopyBoard(boards[0], boards[currentMove]);
13669
13670     blackPlaysFirst = !WhiteOnMove(currentMove);
13671     ResetClocks();
13672     currentMove = forwardMostMove = backwardMostMove = 0;
13673     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13674     DisplayMove(-1);
13675 }
13676
13677 void
13678 ExitAnalyzeMode()
13679 {
13680     /* [DM] icsEngineAnalyze - possible call from other functions */
13681     if (appData.icsEngineAnalyze) {
13682         appData.icsEngineAnalyze = FALSE;
13683
13684         DisplayMessage("",_("Close ICS engine analyze..."));
13685     }
13686     if (first.analysisSupport && first.analyzing) {
13687       SendToProgram("exit\n", &first);
13688       first.analyzing = FALSE;
13689     }
13690     thinkOutput[0] = NULLCHAR;
13691 }
13692
13693 void
13694 EditPositionDone(Boolean fakeRights)
13695 {
13696     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13697
13698     startedFromSetupPosition = TRUE;
13699     InitChessProgram(&first, FALSE);
13700     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13701       boards[0][EP_STATUS] = EP_NONE;
13702       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13703     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13704         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13705         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13706       } else boards[0][CASTLING][2] = NoRights;
13707     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13708         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13709         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13710       } else boards[0][CASTLING][5] = NoRights;
13711     }
13712     SendToProgram("force\n", &first);
13713     if (blackPlaysFirst) {
13714         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13715         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13716         currentMove = forwardMostMove = backwardMostMove = 1;
13717         CopyBoard(boards[1], boards[0]);
13718     } else {
13719         currentMove = forwardMostMove = backwardMostMove = 0;
13720     }
13721     SendBoard(&first, forwardMostMove);
13722     if (appData.debugMode) {
13723         fprintf(debugFP, "EditPosDone\n");
13724     }
13725     DisplayTitle("");
13726     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13727     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13728     gameMode = EditGame;
13729     ModeHighlight();
13730     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13731     ClearHighlights(); /* [AS] */
13732 }
13733
13734 /* Pause for `ms' milliseconds */
13735 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13736 void
13737 TimeDelay(ms)
13738      long ms;
13739 {
13740     TimeMark m1, m2;
13741
13742     GetTimeMark(&m1);
13743     do {
13744         GetTimeMark(&m2);
13745     } while (SubtractTimeMarks(&m2, &m1) < ms);
13746 }
13747
13748 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13749 void
13750 SendMultiLineToICS(buf)
13751      char *buf;
13752 {
13753     char temp[MSG_SIZ+1], *p;
13754     int len;
13755
13756     len = strlen(buf);
13757     if (len > MSG_SIZ)
13758       len = MSG_SIZ;
13759
13760     strncpy(temp, buf, len);
13761     temp[len] = 0;
13762
13763     p = temp;
13764     while (*p) {
13765         if (*p == '\n' || *p == '\r')
13766           *p = ' ';
13767         ++p;
13768     }
13769
13770     strcat(temp, "\n");
13771     SendToICS(temp);
13772     SendToPlayer(temp, strlen(temp));
13773 }
13774
13775 void
13776 SetWhiteToPlayEvent()
13777 {
13778     if (gameMode == EditPosition) {
13779         blackPlaysFirst = FALSE;
13780         DisplayBothClocks();    /* works because currentMove is 0 */
13781     } else if (gameMode == IcsExamining) {
13782         SendToICS(ics_prefix);
13783         SendToICS("tomove white\n");
13784     }
13785 }
13786
13787 void
13788 SetBlackToPlayEvent()
13789 {
13790     if (gameMode == EditPosition) {
13791         blackPlaysFirst = TRUE;
13792         currentMove = 1;        /* kludge */
13793         DisplayBothClocks();
13794         currentMove = 0;
13795     } else if (gameMode == IcsExamining) {
13796         SendToICS(ics_prefix);
13797         SendToICS("tomove black\n");
13798     }
13799 }
13800
13801 void
13802 EditPositionMenuEvent(selection, x, y)
13803      ChessSquare selection;
13804      int x, y;
13805 {
13806     char buf[MSG_SIZ];
13807     ChessSquare piece = boards[0][y][x];
13808
13809     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13810
13811     switch (selection) {
13812       case ClearBoard:
13813         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13814             SendToICS(ics_prefix);
13815             SendToICS("bsetup clear\n");
13816         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13817             SendToICS(ics_prefix);
13818             SendToICS("clearboard\n");
13819         } else {
13820             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13821                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13822                 for (y = 0; y < BOARD_HEIGHT; y++) {
13823                     if (gameMode == IcsExamining) {
13824                         if (boards[currentMove][y][x] != EmptySquare) {
13825                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13826                                     AAA + x, ONE + y);
13827                             SendToICS(buf);
13828                         }
13829                     } else {
13830                         boards[0][y][x] = p;
13831                     }
13832                 }
13833             }
13834         }
13835         if (gameMode == EditPosition) {
13836             DrawPosition(FALSE, boards[0]);
13837         }
13838         break;
13839
13840       case WhitePlay:
13841         SetWhiteToPlayEvent();
13842         break;
13843
13844       case BlackPlay:
13845         SetBlackToPlayEvent();
13846         break;
13847
13848       case EmptySquare:
13849         if (gameMode == IcsExamining) {
13850             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13851             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13852             SendToICS(buf);
13853         } else {
13854             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13855                 if(x == BOARD_LEFT-2) {
13856                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13857                     boards[0][y][1] = 0;
13858                 } else
13859                 if(x == BOARD_RGHT+1) {
13860                     if(y >= gameInfo.holdingsSize) break;
13861                     boards[0][y][BOARD_WIDTH-2] = 0;
13862                 } else break;
13863             }
13864             boards[0][y][x] = EmptySquare;
13865             DrawPosition(FALSE, boards[0]);
13866         }
13867         break;
13868
13869       case PromotePiece:
13870         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13871            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13872             selection = (ChessSquare) (PROMOTED piece);
13873         } else if(piece == EmptySquare) selection = WhiteSilver;
13874         else selection = (ChessSquare)((int)piece - 1);
13875         goto defaultlabel;
13876
13877       case DemotePiece:
13878         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13879            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13880             selection = (ChessSquare) (DEMOTED piece);
13881         } else if(piece == EmptySquare) selection = BlackSilver;
13882         else selection = (ChessSquare)((int)piece + 1);
13883         goto defaultlabel;
13884
13885       case WhiteQueen:
13886       case BlackQueen:
13887         if(gameInfo.variant == VariantShatranj ||
13888            gameInfo.variant == VariantXiangqi  ||
13889            gameInfo.variant == VariantCourier  ||
13890            gameInfo.variant == VariantMakruk     )
13891             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13892         goto defaultlabel;
13893
13894       case WhiteKing:
13895       case BlackKing:
13896         if(gameInfo.variant == VariantXiangqi)
13897             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13898         if(gameInfo.variant == VariantKnightmate)
13899             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13900       default:
13901         defaultlabel:
13902         if (gameMode == IcsExamining) {
13903             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13904             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13905                      PieceToChar(selection), AAA + x, ONE + y);
13906             SendToICS(buf);
13907         } else {
13908             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13909                 int n;
13910                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13911                     n = PieceToNumber(selection - BlackPawn);
13912                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13913                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13914                     boards[0][BOARD_HEIGHT-1-n][1]++;
13915                 } else
13916                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13917                     n = PieceToNumber(selection);
13918                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13919                     boards[0][n][BOARD_WIDTH-1] = selection;
13920                     boards[0][n][BOARD_WIDTH-2]++;
13921                 }
13922             } else
13923             boards[0][y][x] = selection;
13924             DrawPosition(TRUE, boards[0]);
13925         }
13926         break;
13927     }
13928 }
13929
13930
13931 void
13932 DropMenuEvent(selection, x, y)
13933      ChessSquare selection;
13934      int x, y;
13935 {
13936     ChessMove moveType;
13937
13938     switch (gameMode) {
13939       case IcsPlayingWhite:
13940       case MachinePlaysBlack:
13941         if (!WhiteOnMove(currentMove)) {
13942             DisplayMoveError(_("It is Black's turn"));
13943             return;
13944         }
13945         moveType = WhiteDrop;
13946         break;
13947       case IcsPlayingBlack:
13948       case MachinePlaysWhite:
13949         if (WhiteOnMove(currentMove)) {
13950             DisplayMoveError(_("It is White's turn"));
13951             return;
13952         }
13953         moveType = BlackDrop;
13954         break;
13955       case EditGame:
13956         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13957         break;
13958       default:
13959         return;
13960     }
13961
13962     if (moveType == BlackDrop && selection < BlackPawn) {
13963       selection = (ChessSquare) ((int) selection
13964                                  + (int) BlackPawn - (int) WhitePawn);
13965     }
13966     if (boards[currentMove][y][x] != EmptySquare) {
13967         DisplayMoveError(_("That square is occupied"));
13968         return;
13969     }
13970
13971     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13972 }
13973
13974 void
13975 AcceptEvent()
13976 {
13977     /* Accept a pending offer of any kind from opponent */
13978
13979     if (appData.icsActive) {
13980         SendToICS(ics_prefix);
13981         SendToICS("accept\n");
13982     } else if (cmailMsgLoaded) {
13983         if (currentMove == cmailOldMove &&
13984             commentList[cmailOldMove] != NULL &&
13985             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13986                    "Black offers a draw" : "White offers a draw")) {
13987             TruncateGame();
13988             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13989             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13990         } else {
13991             DisplayError(_("There is no pending offer on this move"), 0);
13992             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13993         }
13994     } else {
13995         /* Not used for offers from chess program */
13996     }
13997 }
13998
13999 void
14000 DeclineEvent()
14001 {
14002     /* Decline a pending offer of any kind from opponent */
14003
14004     if (appData.icsActive) {
14005         SendToICS(ics_prefix);
14006         SendToICS("decline\n");
14007     } else if (cmailMsgLoaded) {
14008         if (currentMove == cmailOldMove &&
14009             commentList[cmailOldMove] != NULL &&
14010             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14011                    "Black offers a draw" : "White offers a draw")) {
14012 #ifdef NOTDEF
14013             AppendComment(cmailOldMove, "Draw declined", TRUE);
14014             DisplayComment(cmailOldMove - 1, "Draw declined");
14015 #endif /*NOTDEF*/
14016         } else {
14017             DisplayError(_("There is no pending offer on this move"), 0);
14018         }
14019     } else {
14020         /* Not used for offers from chess program */
14021     }
14022 }
14023
14024 void
14025 RematchEvent()
14026 {
14027     /* Issue ICS rematch command */
14028     if (appData.icsActive) {
14029         SendToICS(ics_prefix);
14030         SendToICS("rematch\n");
14031     }
14032 }
14033
14034 void
14035 CallFlagEvent()
14036 {
14037     /* Call your opponent's flag (claim a win on time) */
14038     if (appData.icsActive) {
14039         SendToICS(ics_prefix);
14040         SendToICS("flag\n");
14041     } else {
14042         switch (gameMode) {
14043           default:
14044             return;
14045           case MachinePlaysWhite:
14046             if (whiteFlag) {
14047                 if (blackFlag)
14048                   GameEnds(GameIsDrawn, "Both players ran out of time",
14049                            GE_PLAYER);
14050                 else
14051                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14052             } else {
14053                 DisplayError(_("Your opponent is not out of time"), 0);
14054             }
14055             break;
14056           case MachinePlaysBlack:
14057             if (blackFlag) {
14058                 if (whiteFlag)
14059                   GameEnds(GameIsDrawn, "Both players ran out of time",
14060                            GE_PLAYER);
14061                 else
14062                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14063             } else {
14064                 DisplayError(_("Your opponent is not out of time"), 0);
14065             }
14066             break;
14067         }
14068     }
14069 }
14070
14071 void
14072 ClockClick(int which)
14073 {       // [HGM] code moved to back-end from winboard.c
14074         if(which) { // black clock
14075           if (gameMode == EditPosition || gameMode == IcsExamining) {
14076             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14077             SetBlackToPlayEvent();
14078           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14079           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14080           } else if (shiftKey) {
14081             AdjustClock(which, -1);
14082           } else if (gameMode == IcsPlayingWhite ||
14083                      gameMode == MachinePlaysBlack) {
14084             CallFlagEvent();
14085           }
14086         } else { // white clock
14087           if (gameMode == EditPosition || gameMode == IcsExamining) {
14088             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14089             SetWhiteToPlayEvent();
14090           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14091           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14092           } else if (shiftKey) {
14093             AdjustClock(which, -1);
14094           } else if (gameMode == IcsPlayingBlack ||
14095                    gameMode == MachinePlaysWhite) {
14096             CallFlagEvent();
14097           }
14098         }
14099 }
14100
14101 void
14102 DrawEvent()
14103 {
14104     /* Offer draw or accept pending draw offer from opponent */
14105
14106     if (appData.icsActive) {
14107         /* Note: tournament rules require draw offers to be
14108            made after you make your move but before you punch
14109            your clock.  Currently ICS doesn't let you do that;
14110            instead, you immediately punch your clock after making
14111            a move, but you can offer a draw at any time. */
14112
14113         SendToICS(ics_prefix);
14114         SendToICS("draw\n");
14115         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14116     } else if (cmailMsgLoaded) {
14117         if (currentMove == cmailOldMove &&
14118             commentList[cmailOldMove] != NULL &&
14119             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14120                    "Black offers a draw" : "White offers a draw")) {
14121             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14122             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14123         } else if (currentMove == cmailOldMove + 1) {
14124             char *offer = WhiteOnMove(cmailOldMove) ?
14125               "White offers a draw" : "Black offers a draw";
14126             AppendComment(currentMove, offer, TRUE);
14127             DisplayComment(currentMove - 1, offer);
14128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14129         } else {
14130             DisplayError(_("You must make your move before offering a draw"), 0);
14131             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14132         }
14133     } else if (first.offeredDraw) {
14134         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14135     } else {
14136         if (first.sendDrawOffers) {
14137             SendToProgram("draw\n", &first);
14138             userOfferedDraw = TRUE;
14139         }
14140     }
14141 }
14142
14143 void
14144 AdjournEvent()
14145 {
14146     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14147
14148     if (appData.icsActive) {
14149         SendToICS(ics_prefix);
14150         SendToICS("adjourn\n");
14151     } else {
14152         /* Currently GNU Chess doesn't offer or accept Adjourns */
14153     }
14154 }
14155
14156
14157 void
14158 AbortEvent()
14159 {
14160     /* Offer Abort or accept pending Abort offer from opponent */
14161
14162     if (appData.icsActive) {
14163         SendToICS(ics_prefix);
14164         SendToICS("abort\n");
14165     } else {
14166         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14167     }
14168 }
14169
14170 void
14171 ResignEvent()
14172 {
14173     /* Resign.  You can do this even if it's not your turn. */
14174
14175     if (appData.icsActive) {
14176         SendToICS(ics_prefix);
14177         SendToICS("resign\n");
14178     } else {
14179         switch (gameMode) {
14180           case MachinePlaysWhite:
14181             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14182             break;
14183           case MachinePlaysBlack:
14184             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14185             break;
14186           case EditGame:
14187             if (cmailMsgLoaded) {
14188                 TruncateGame();
14189                 if (WhiteOnMove(cmailOldMove)) {
14190                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14191                 } else {
14192                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14193                 }
14194                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14195             }
14196             break;
14197           default:
14198             break;
14199         }
14200     }
14201 }
14202
14203
14204 void
14205 StopObservingEvent()
14206 {
14207     /* Stop observing current games */
14208     SendToICS(ics_prefix);
14209     SendToICS("unobserve\n");
14210 }
14211
14212 void
14213 StopExaminingEvent()
14214 {
14215     /* Stop observing current game */
14216     SendToICS(ics_prefix);
14217     SendToICS("unexamine\n");
14218 }
14219
14220 void
14221 ForwardInner(target)
14222      int target;
14223 {
14224     int limit;
14225
14226     if (appData.debugMode)
14227         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14228                 target, currentMove, forwardMostMove);
14229
14230     if (gameMode == EditPosition)
14231       return;
14232
14233     MarkTargetSquares(1);
14234
14235     if (gameMode == PlayFromGameFile && !pausing)
14236       PauseEvent();
14237
14238     if (gameMode == IcsExamining && pausing)
14239       limit = pauseExamForwardMostMove;
14240     else
14241       limit = forwardMostMove;
14242
14243     if (target > limit) target = limit;
14244
14245     if (target > 0 && moveList[target - 1][0]) {
14246         int fromX, fromY, toX, toY;
14247         toX = moveList[target - 1][2] - AAA;
14248         toY = moveList[target - 1][3] - ONE;
14249         if (moveList[target - 1][1] == '@') {
14250             if (appData.highlightLastMove) {
14251                 SetHighlights(-1, -1, toX, toY);
14252             }
14253         } else {
14254             fromX = moveList[target - 1][0] - AAA;
14255             fromY = moveList[target - 1][1] - ONE;
14256             if (target == currentMove + 1) {
14257                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14258             }
14259             if (appData.highlightLastMove) {
14260                 SetHighlights(fromX, fromY, toX, toY);
14261             }
14262         }
14263     }
14264     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14265         gameMode == Training || gameMode == PlayFromGameFile ||
14266         gameMode == AnalyzeFile) {
14267         while (currentMove < target) {
14268             SendMoveToProgram(currentMove++, &first);
14269         }
14270     } else {
14271         currentMove = target;
14272     }
14273
14274     if (gameMode == EditGame || gameMode == EndOfGame) {
14275         whiteTimeRemaining = timeRemaining[0][currentMove];
14276         blackTimeRemaining = timeRemaining[1][currentMove];
14277     }
14278     DisplayBothClocks();
14279     DisplayMove(currentMove - 1);
14280     DrawPosition(FALSE, boards[currentMove]);
14281     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14282     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14283         DisplayComment(currentMove - 1, commentList[currentMove]);
14284     }
14285 }
14286
14287
14288 void
14289 ForwardEvent()
14290 {
14291     if (gameMode == IcsExamining && !pausing) {
14292         SendToICS(ics_prefix);
14293         SendToICS("forward\n");
14294     } else {
14295         ForwardInner(currentMove + 1);
14296     }
14297 }
14298
14299 void
14300 ToEndEvent()
14301 {
14302     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14303         /* to optimze, we temporarily turn off analysis mode while we feed
14304          * the remaining moves to the engine. Otherwise we get analysis output
14305          * after each move.
14306          */
14307         if (first.analysisSupport) {
14308           SendToProgram("exit\nforce\n", &first);
14309           first.analyzing = FALSE;
14310         }
14311     }
14312
14313     if (gameMode == IcsExamining && !pausing) {
14314         SendToICS(ics_prefix);
14315         SendToICS("forward 999999\n");
14316     } else {
14317         ForwardInner(forwardMostMove);
14318     }
14319
14320     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14321         /* we have fed all the moves, so reactivate analysis mode */
14322         SendToProgram("analyze\n", &first);
14323         first.analyzing = TRUE;
14324         /*first.maybeThinking = TRUE;*/
14325         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14326     }
14327 }
14328
14329 void
14330 BackwardInner(target)
14331      int target;
14332 {
14333     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14334
14335     if (appData.debugMode)
14336         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14337                 target, currentMove, forwardMostMove);
14338
14339     if (gameMode == EditPosition) return;
14340     MarkTargetSquares(1);
14341     if (currentMove <= backwardMostMove) {
14342         ClearHighlights();
14343         DrawPosition(full_redraw, boards[currentMove]);
14344         return;
14345     }
14346     if (gameMode == PlayFromGameFile && !pausing)
14347       PauseEvent();
14348
14349     if (moveList[target][0]) {
14350         int fromX, fromY, toX, toY;
14351         toX = moveList[target][2] - AAA;
14352         toY = moveList[target][3] - ONE;
14353         if (moveList[target][1] == '@') {
14354             if (appData.highlightLastMove) {
14355                 SetHighlights(-1, -1, toX, toY);
14356             }
14357         } else {
14358             fromX = moveList[target][0] - AAA;
14359             fromY = moveList[target][1] - ONE;
14360             if (target == currentMove - 1) {
14361                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14362             }
14363             if (appData.highlightLastMove) {
14364                 SetHighlights(fromX, fromY, toX, toY);
14365             }
14366         }
14367     }
14368     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14369         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14370         while (currentMove > target) {
14371             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14372                 // null move cannot be undone. Reload program with move history before it.
14373                 int i;
14374                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14375                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14376                 }
14377                 SendBoard(&first, i); 
14378                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14379                 break;
14380             }
14381             SendToProgram("undo\n", &first);
14382             currentMove--;
14383         }
14384     } else {
14385         currentMove = target;
14386     }
14387
14388     if (gameMode == EditGame || gameMode == EndOfGame) {
14389         whiteTimeRemaining = timeRemaining[0][currentMove];
14390         blackTimeRemaining = timeRemaining[1][currentMove];
14391     }
14392     DisplayBothClocks();
14393     DisplayMove(currentMove - 1);
14394     DrawPosition(full_redraw, boards[currentMove]);
14395     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14396     // [HGM] PV info: routine tests if comment empty
14397     DisplayComment(currentMove - 1, commentList[currentMove]);
14398 }
14399
14400 void
14401 BackwardEvent()
14402 {
14403     if (gameMode == IcsExamining && !pausing) {
14404         SendToICS(ics_prefix);
14405         SendToICS("backward\n");
14406     } else {
14407         BackwardInner(currentMove - 1);
14408     }
14409 }
14410
14411 void
14412 ToStartEvent()
14413 {
14414     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14415         /* to optimize, we temporarily turn off analysis mode while we undo
14416          * all the moves. Otherwise we get analysis output after each undo.
14417          */
14418         if (first.analysisSupport) {
14419           SendToProgram("exit\nforce\n", &first);
14420           first.analyzing = FALSE;
14421         }
14422     }
14423
14424     if (gameMode == IcsExamining && !pausing) {
14425         SendToICS(ics_prefix);
14426         SendToICS("backward 999999\n");
14427     } else {
14428         BackwardInner(backwardMostMove);
14429     }
14430
14431     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14432         /* we have fed all the moves, so reactivate analysis mode */
14433         SendToProgram("analyze\n", &first);
14434         first.analyzing = TRUE;
14435         /*first.maybeThinking = TRUE;*/
14436         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14437     }
14438 }
14439
14440 void
14441 ToNrEvent(int to)
14442 {
14443   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14444   if (to >= forwardMostMove) to = forwardMostMove;
14445   if (to <= backwardMostMove) to = backwardMostMove;
14446   if (to < currentMove) {
14447     BackwardInner(to);
14448   } else {
14449     ForwardInner(to);
14450   }
14451 }
14452
14453 void
14454 RevertEvent(Boolean annotate)
14455 {
14456     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14457         return;
14458     }
14459     if (gameMode != IcsExamining) {
14460         DisplayError(_("You are not examining a game"), 0);
14461         return;
14462     }
14463     if (pausing) {
14464         DisplayError(_("You can't revert while pausing"), 0);
14465         return;
14466     }
14467     SendToICS(ics_prefix);
14468     SendToICS("revert\n");
14469 }
14470
14471 void
14472 RetractMoveEvent()
14473 {
14474     switch (gameMode) {
14475       case MachinePlaysWhite:
14476       case MachinePlaysBlack:
14477         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14478             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14479             return;
14480         }
14481         if (forwardMostMove < 2) return;
14482         currentMove = forwardMostMove = forwardMostMove - 2;
14483         whiteTimeRemaining = timeRemaining[0][currentMove];
14484         blackTimeRemaining = timeRemaining[1][currentMove];
14485         DisplayBothClocks();
14486         DisplayMove(currentMove - 1);
14487         ClearHighlights();/*!! could figure this out*/
14488         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14489         SendToProgram("remove\n", &first);
14490         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14491         break;
14492
14493       case BeginningOfGame:
14494       default:
14495         break;
14496
14497       case IcsPlayingWhite:
14498       case IcsPlayingBlack:
14499         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14500             SendToICS(ics_prefix);
14501             SendToICS("takeback 2\n");
14502         } else {
14503             SendToICS(ics_prefix);
14504             SendToICS("takeback 1\n");
14505         }
14506         break;
14507     }
14508 }
14509
14510 void
14511 MoveNowEvent()
14512 {
14513     ChessProgramState *cps;
14514
14515     switch (gameMode) {
14516       case MachinePlaysWhite:
14517         if (!WhiteOnMove(forwardMostMove)) {
14518             DisplayError(_("It is your turn"), 0);
14519             return;
14520         }
14521         cps = &first;
14522         break;
14523       case MachinePlaysBlack:
14524         if (WhiteOnMove(forwardMostMove)) {
14525             DisplayError(_("It is your turn"), 0);
14526             return;
14527         }
14528         cps = &first;
14529         break;
14530       case TwoMachinesPlay:
14531         if (WhiteOnMove(forwardMostMove) ==
14532             (first.twoMachinesColor[0] == 'w')) {
14533             cps = &first;
14534         } else {
14535             cps = &second;
14536         }
14537         break;
14538       case BeginningOfGame:
14539       default:
14540         return;
14541     }
14542     SendToProgram("?\n", cps);
14543 }
14544
14545 void
14546 TruncateGameEvent()
14547 {
14548     EditGameEvent();
14549     if (gameMode != EditGame) return;
14550     TruncateGame();
14551 }
14552
14553 void
14554 TruncateGame()
14555 {
14556     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14557     if (forwardMostMove > currentMove) {
14558         if (gameInfo.resultDetails != NULL) {
14559             free(gameInfo.resultDetails);
14560             gameInfo.resultDetails = NULL;
14561             gameInfo.result = GameUnfinished;
14562         }
14563         forwardMostMove = currentMove;
14564         HistorySet(parseList, backwardMostMove, forwardMostMove,
14565                    currentMove-1);
14566     }
14567 }
14568
14569 void
14570 HintEvent()
14571 {
14572     if (appData.noChessProgram) return;
14573     switch (gameMode) {
14574       case MachinePlaysWhite:
14575         if (WhiteOnMove(forwardMostMove)) {
14576             DisplayError(_("Wait until your turn"), 0);
14577             return;
14578         }
14579         break;
14580       case BeginningOfGame:
14581       case MachinePlaysBlack:
14582         if (!WhiteOnMove(forwardMostMove)) {
14583             DisplayError(_("Wait until your turn"), 0);
14584             return;
14585         }
14586         break;
14587       default:
14588         DisplayError(_("No hint available"), 0);
14589         return;
14590     }
14591     SendToProgram("hint\n", &first);
14592     hintRequested = TRUE;
14593 }
14594
14595 void
14596 BookEvent()
14597 {
14598     if (appData.noChessProgram) return;
14599     switch (gameMode) {
14600       case MachinePlaysWhite:
14601         if (WhiteOnMove(forwardMostMove)) {
14602             DisplayError(_("Wait until your turn"), 0);
14603             return;
14604         }
14605         break;
14606       case BeginningOfGame:
14607       case MachinePlaysBlack:
14608         if (!WhiteOnMove(forwardMostMove)) {
14609             DisplayError(_("Wait until your turn"), 0);
14610             return;
14611         }
14612         break;
14613       case EditPosition:
14614         EditPositionDone(TRUE);
14615         break;
14616       case TwoMachinesPlay:
14617         return;
14618       default:
14619         break;
14620     }
14621     SendToProgram("bk\n", &first);
14622     bookOutput[0] = NULLCHAR;
14623     bookRequested = TRUE;
14624 }
14625
14626 void
14627 AboutGameEvent()
14628 {
14629     char *tags = PGNTags(&gameInfo);
14630     TagsPopUp(tags, CmailMsg());
14631     free(tags);
14632 }
14633
14634 /* end button procedures */
14635
14636 void
14637 PrintPosition(fp, move)
14638      FILE *fp;
14639      int move;
14640 {
14641     int i, j;
14642
14643     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14644         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14645             char c = PieceToChar(boards[move][i][j]);
14646             fputc(c == 'x' ? '.' : c, fp);
14647             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14648         }
14649     }
14650     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14651       fprintf(fp, "white to play\n");
14652     else
14653       fprintf(fp, "black to play\n");
14654 }
14655
14656 void
14657 PrintOpponents(fp)
14658      FILE *fp;
14659 {
14660     if (gameInfo.white != NULL) {
14661         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14662     } else {
14663         fprintf(fp, "\n");
14664     }
14665 }
14666
14667 /* Find last component of program's own name, using some heuristics */
14668 void
14669 TidyProgramName(prog, host, buf)
14670      char *prog, *host, buf[MSG_SIZ];
14671 {
14672     char *p, *q;
14673     int local = (strcmp(host, "localhost") == 0);
14674     while (!local && (p = strchr(prog, ';')) != NULL) {
14675         p++;
14676         while (*p == ' ') p++;
14677         prog = p;
14678     }
14679     if (*prog == '"' || *prog == '\'') {
14680         q = strchr(prog + 1, *prog);
14681     } else {
14682         q = strchr(prog, ' ');
14683     }
14684     if (q == NULL) q = prog + strlen(prog);
14685     p = q;
14686     while (p >= prog && *p != '/' && *p != '\\') p--;
14687     p++;
14688     if(p == prog && *p == '"') p++;
14689     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14690     memcpy(buf, p, q - p);
14691     buf[q - p] = NULLCHAR;
14692     if (!local) {
14693         strcat(buf, "@");
14694         strcat(buf, host);
14695     }
14696 }
14697
14698 char *
14699 TimeControlTagValue()
14700 {
14701     char buf[MSG_SIZ];
14702     if (!appData.clockMode) {
14703       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14704     } else if (movesPerSession > 0) {
14705       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14706     } else if (timeIncrement == 0) {
14707       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14708     } else {
14709       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14710     }
14711     return StrSave(buf);
14712 }
14713
14714 void
14715 SetGameInfo()
14716 {
14717     /* This routine is used only for certain modes */
14718     VariantClass v = gameInfo.variant;
14719     ChessMove r = GameUnfinished;
14720     char *p = NULL;
14721
14722     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14723         r = gameInfo.result;
14724         p = gameInfo.resultDetails;
14725         gameInfo.resultDetails = NULL;
14726     }
14727     ClearGameInfo(&gameInfo);
14728     gameInfo.variant = v;
14729
14730     switch (gameMode) {
14731       case MachinePlaysWhite:
14732         gameInfo.event = StrSave( appData.pgnEventHeader );
14733         gameInfo.site = StrSave(HostName());
14734         gameInfo.date = PGNDate();
14735         gameInfo.round = StrSave("-");
14736         gameInfo.white = StrSave(first.tidy);
14737         gameInfo.black = StrSave(UserName());
14738         gameInfo.timeControl = TimeControlTagValue();
14739         break;
14740
14741       case MachinePlaysBlack:
14742         gameInfo.event = StrSave( appData.pgnEventHeader );
14743         gameInfo.site = StrSave(HostName());
14744         gameInfo.date = PGNDate();
14745         gameInfo.round = StrSave("-");
14746         gameInfo.white = StrSave(UserName());
14747         gameInfo.black = StrSave(first.tidy);
14748         gameInfo.timeControl = TimeControlTagValue();
14749         break;
14750
14751       case TwoMachinesPlay:
14752         gameInfo.event = StrSave( appData.pgnEventHeader );
14753         gameInfo.site = StrSave(HostName());
14754         gameInfo.date = PGNDate();
14755         if (roundNr > 0) {
14756             char buf[MSG_SIZ];
14757             snprintf(buf, MSG_SIZ, "%d", roundNr);
14758             gameInfo.round = StrSave(buf);
14759         } else {
14760             gameInfo.round = StrSave("-");
14761         }
14762         if (first.twoMachinesColor[0] == 'w') {
14763             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14764             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14765         } else {
14766             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14767             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14768         }
14769         gameInfo.timeControl = TimeControlTagValue();
14770         break;
14771
14772       case EditGame:
14773         gameInfo.event = StrSave("Edited game");
14774         gameInfo.site = StrSave(HostName());
14775         gameInfo.date = PGNDate();
14776         gameInfo.round = StrSave("-");
14777         gameInfo.white = StrSave("-");
14778         gameInfo.black = StrSave("-");
14779         gameInfo.result = r;
14780         gameInfo.resultDetails = p;
14781         break;
14782
14783       case EditPosition:
14784         gameInfo.event = StrSave("Edited position");
14785         gameInfo.site = StrSave(HostName());
14786         gameInfo.date = PGNDate();
14787         gameInfo.round = StrSave("-");
14788         gameInfo.white = StrSave("-");
14789         gameInfo.black = StrSave("-");
14790         break;
14791
14792       case IcsPlayingWhite:
14793       case IcsPlayingBlack:
14794       case IcsObserving:
14795       case IcsExamining:
14796         break;
14797
14798       case PlayFromGameFile:
14799         gameInfo.event = StrSave("Game from non-PGN file");
14800         gameInfo.site = StrSave(HostName());
14801         gameInfo.date = PGNDate();
14802         gameInfo.round = StrSave("-");
14803         gameInfo.white = StrSave("?");
14804         gameInfo.black = StrSave("?");
14805         break;
14806
14807       default:
14808         break;
14809     }
14810 }
14811
14812 void
14813 ReplaceComment(index, text)
14814      int index;
14815      char *text;
14816 {
14817     int len;
14818     char *p;
14819     float score;
14820
14821     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14822        pvInfoList[index-1].depth == len &&
14823        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14824        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14825     while (*text == '\n') text++;
14826     len = strlen(text);
14827     while (len > 0 && text[len - 1] == '\n') len--;
14828
14829     if (commentList[index] != NULL)
14830       free(commentList[index]);
14831
14832     if (len == 0) {
14833         commentList[index] = NULL;
14834         return;
14835     }
14836   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14837       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14838       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14839     commentList[index] = (char *) malloc(len + 2);
14840     strncpy(commentList[index], text, len);
14841     commentList[index][len] = '\n';
14842     commentList[index][len + 1] = NULLCHAR;
14843   } else {
14844     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14845     char *p;
14846     commentList[index] = (char *) malloc(len + 7);
14847     safeStrCpy(commentList[index], "{\n", 3);
14848     safeStrCpy(commentList[index]+2, text, len+1);
14849     commentList[index][len+2] = NULLCHAR;
14850     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14851     strcat(commentList[index], "\n}\n");
14852   }
14853 }
14854
14855 void
14856 CrushCRs(text)
14857      char *text;
14858 {
14859   char *p = text;
14860   char *q = text;
14861   char ch;
14862
14863   do {
14864     ch = *p++;
14865     if (ch == '\r') continue;
14866     *q++ = ch;
14867   } while (ch != '\0');
14868 }
14869
14870 void
14871 AppendComment(index, text, addBraces)
14872      int index;
14873      char *text;
14874      Boolean addBraces; // [HGM] braces: tells if we should add {}
14875 {
14876     int oldlen, len;
14877     char *old;
14878
14879 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14880     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14881
14882     CrushCRs(text);
14883     while (*text == '\n') text++;
14884     len = strlen(text);
14885     while (len > 0 && text[len - 1] == '\n') len--;
14886
14887     if (len == 0) return;
14888
14889     if (commentList[index] != NULL) {
14890       Boolean addClosingBrace = addBraces;
14891         old = commentList[index];
14892         oldlen = strlen(old);
14893         while(commentList[index][oldlen-1] ==  '\n')
14894           commentList[index][--oldlen] = NULLCHAR;
14895         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14896         safeStrCpy(commentList[index], old, oldlen + len + 6);
14897         free(old);
14898         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14899         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14900           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14901           while (*text == '\n') { text++; len--; }
14902           commentList[index][--oldlen] = NULLCHAR;
14903       }
14904         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14905         else          strcat(commentList[index], "\n");
14906         strcat(commentList[index], text);
14907         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14908         else          strcat(commentList[index], "\n");
14909     } else {
14910         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14911         if(addBraces)
14912           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14913         else commentList[index][0] = NULLCHAR;
14914         strcat(commentList[index], text);
14915         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14916         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14917     }
14918 }
14919
14920 static char * FindStr( char * text, char * sub_text )
14921 {
14922     char * result = strstr( text, sub_text );
14923
14924     if( result != NULL ) {
14925         result += strlen( sub_text );
14926     }
14927
14928     return result;
14929 }
14930
14931 /* [AS] Try to extract PV info from PGN comment */
14932 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14933 char *GetInfoFromComment( int index, char * text )
14934 {
14935     char * sep = text, *p;
14936
14937     if( text != NULL && index > 0 ) {
14938         int score = 0;
14939         int depth = 0;
14940         int time = -1, sec = 0, deci;
14941         char * s_eval = FindStr( text, "[%eval " );
14942         char * s_emt = FindStr( text, "[%emt " );
14943
14944         if( s_eval != NULL || s_emt != NULL ) {
14945             /* New style */
14946             char delim;
14947
14948             if( s_eval != NULL ) {
14949                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14950                     return text;
14951                 }
14952
14953                 if( delim != ']' ) {
14954                     return text;
14955                 }
14956             }
14957
14958             if( s_emt != NULL ) {
14959             }
14960                 return text;
14961         }
14962         else {
14963             /* We expect something like: [+|-]nnn.nn/dd */
14964             int score_lo = 0;
14965
14966             if(*text != '{') return text; // [HGM] braces: must be normal comment
14967
14968             sep = strchr( text, '/' );
14969             if( sep == NULL || sep < (text+4) ) {
14970                 return text;
14971             }
14972
14973             p = text;
14974             if(p[1] == '(') { // comment starts with PV
14975                p = strchr(p, ')'); // locate end of PV
14976                if(p == NULL || sep < p+5) return text;
14977                // at this point we have something like "{(.*) +0.23/6 ..."
14978                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14979                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14980                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14981             }
14982             time = -1; sec = -1; deci = -1;
14983             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14984                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14985                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14986                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14987                 return text;
14988             }
14989
14990             if( score_lo < 0 || score_lo >= 100 ) {
14991                 return text;
14992             }
14993
14994             if(sec >= 0) time = 600*time + 10*sec; else
14995             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14996
14997             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14998
14999             /* [HGM] PV time: now locate end of PV info */
15000             while( *++sep >= '0' && *sep <= '9'); // strip depth
15001             if(time >= 0)
15002             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15003             if(sec >= 0)
15004             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15005             if(deci >= 0)
15006             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15007             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15008         }
15009
15010         if( depth <= 0 ) {
15011             return text;
15012         }
15013
15014         if( time < 0 ) {
15015             time = -1;
15016         }
15017
15018         pvInfoList[index-1].depth = depth;
15019         pvInfoList[index-1].score = score;
15020         pvInfoList[index-1].time  = 10*time; // centi-sec
15021         if(*sep == '}') *sep = 0; else *--sep = '{';
15022         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15023     }
15024     return sep;
15025 }
15026
15027 void
15028 SendToProgram(message, cps)
15029      char *message;
15030      ChessProgramState *cps;
15031 {
15032     int count, outCount, error;
15033     char buf[MSG_SIZ];
15034
15035     if (cps->pr == NoProc) return;
15036     Attention(cps);
15037
15038     if (appData.debugMode) {
15039         TimeMark now;
15040         GetTimeMark(&now);
15041         fprintf(debugFP, "%ld >%-6s: %s",
15042                 SubtractTimeMarks(&now, &programStartTime),
15043                 cps->which, message);
15044     }
15045
15046     count = strlen(message);
15047     outCount = OutputToProcess(cps->pr, message, count, &error);
15048     if (outCount < count && !exiting
15049                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15050       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15051       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15052         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15053             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15054                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15055                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15056                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15057             } else {
15058                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15059                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15060                 gameInfo.result = res;
15061             }
15062             gameInfo.resultDetails = StrSave(buf);
15063         }
15064         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15065         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15066     }
15067 }
15068
15069 void
15070 ReceiveFromProgram(isr, closure, message, count, error)
15071      InputSourceRef isr;
15072      VOIDSTAR closure;
15073      char *message;
15074      int count;
15075      int error;
15076 {
15077     char *end_str;
15078     char buf[MSG_SIZ];
15079     ChessProgramState *cps = (ChessProgramState *)closure;
15080
15081     if (isr != cps->isr) return; /* Killed intentionally */
15082     if (count <= 0) {
15083         if (count == 0) {
15084             RemoveInputSource(cps->isr);
15085             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15086             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15087                     _(cps->which), cps->program);
15088         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15089                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15090                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15091                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15092                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15093                 } else {
15094                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15095                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15096                     gameInfo.result = res;
15097                 }
15098                 gameInfo.resultDetails = StrSave(buf);
15099             }
15100             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15101             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15102         } else {
15103             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15104                     _(cps->which), cps->program);
15105             RemoveInputSource(cps->isr);
15106
15107             /* [AS] Program is misbehaving badly... kill it */
15108             if( count == -2 ) {
15109                 DestroyChildProcess( cps->pr, 9 );
15110                 cps->pr = NoProc;
15111             }
15112
15113             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15114         }
15115         return;
15116     }
15117
15118     if ((end_str = strchr(message, '\r')) != NULL)
15119       *end_str = NULLCHAR;
15120     if ((end_str = strchr(message, '\n')) != NULL)
15121       *end_str = NULLCHAR;
15122
15123     if (appData.debugMode) {
15124         TimeMark now; int print = 1;
15125         char *quote = ""; char c; int i;
15126
15127         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15128                 char start = message[0];
15129                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15130                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15131                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15132                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15133                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15134                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15135                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15136                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15137                    sscanf(message, "hint: %c", &c)!=1 && 
15138                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15139                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15140                     print = (appData.engineComments >= 2);
15141                 }
15142                 message[0] = start; // restore original message
15143         }
15144         if(print) {
15145                 GetTimeMark(&now);
15146                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15147                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15148                         quote,
15149                         message);
15150         }
15151     }
15152
15153     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15154     if (appData.icsEngineAnalyze) {
15155         if (strstr(message, "whisper") != NULL ||
15156              strstr(message, "kibitz") != NULL ||
15157             strstr(message, "tellics") != NULL) return;
15158     }
15159
15160     HandleMachineMove(message, cps);
15161 }
15162
15163
15164 void
15165 SendTimeControl(cps, mps, tc, inc, sd, st)
15166      ChessProgramState *cps;
15167      int mps, inc, sd, st;
15168      long tc;
15169 {
15170     char buf[MSG_SIZ];
15171     int seconds;
15172
15173     if( timeControl_2 > 0 ) {
15174         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15175             tc = timeControl_2;
15176         }
15177     }
15178     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15179     inc /= cps->timeOdds;
15180     st  /= cps->timeOdds;
15181
15182     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15183
15184     if (st > 0) {
15185       /* Set exact time per move, normally using st command */
15186       if (cps->stKludge) {
15187         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15188         seconds = st % 60;
15189         if (seconds == 0) {
15190           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15191         } else {
15192           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15193         }
15194       } else {
15195         snprintf(buf, MSG_SIZ, "st %d\n", st);
15196       }
15197     } else {
15198       /* Set conventional or incremental time control, using level command */
15199       if (seconds == 0) {
15200         /* Note old gnuchess bug -- minutes:seconds used to not work.
15201            Fixed in later versions, but still avoid :seconds
15202            when seconds is 0. */
15203         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15204       } else {
15205         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15206                  seconds, inc/1000.);
15207       }
15208     }
15209     SendToProgram(buf, cps);
15210
15211     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15212     /* Orthogonally, limit search to given depth */
15213     if (sd > 0) {
15214       if (cps->sdKludge) {
15215         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15216       } else {
15217         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15218       }
15219       SendToProgram(buf, cps);
15220     }
15221
15222     if(cps->nps >= 0) { /* [HGM] nps */
15223         if(cps->supportsNPS == FALSE)
15224           cps->nps = -1; // don't use if engine explicitly says not supported!
15225         else {
15226           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15227           SendToProgram(buf, cps);
15228         }
15229     }
15230 }
15231
15232 ChessProgramState *WhitePlayer()
15233 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15234 {
15235     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15236        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15237         return &second;
15238     return &first;
15239 }
15240
15241 void
15242 SendTimeRemaining(cps, machineWhite)
15243      ChessProgramState *cps;
15244      int /*boolean*/ machineWhite;
15245 {
15246     char message[MSG_SIZ];
15247     long time, otime;
15248
15249     /* Note: this routine must be called when the clocks are stopped
15250        or when they have *just* been set or switched; otherwise
15251        it will be off by the time since the current tick started.
15252     */
15253     if (machineWhite) {
15254         time = whiteTimeRemaining / 10;
15255         otime = blackTimeRemaining / 10;
15256     } else {
15257         time = blackTimeRemaining / 10;
15258         otime = whiteTimeRemaining / 10;
15259     }
15260     /* [HGM] translate opponent's time by time-odds factor */
15261     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15262     if (appData.debugMode) {
15263         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15264     }
15265
15266     if (time <= 0) time = 1;
15267     if (otime <= 0) otime = 1;
15268
15269     snprintf(message, MSG_SIZ, "time %ld\n", time);
15270     SendToProgram(message, cps);
15271
15272     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15273     SendToProgram(message, cps);
15274 }
15275
15276 int
15277 BoolFeature(p, name, loc, cps)
15278      char **p;
15279      char *name;
15280      int *loc;
15281      ChessProgramState *cps;
15282 {
15283   char buf[MSG_SIZ];
15284   int len = strlen(name);
15285   int val;
15286
15287   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15288     (*p) += len + 1;
15289     sscanf(*p, "%d", &val);
15290     *loc = (val != 0);
15291     while (**p && **p != ' ')
15292       (*p)++;
15293     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15294     SendToProgram(buf, cps);
15295     return TRUE;
15296   }
15297   return FALSE;
15298 }
15299
15300 int
15301 IntFeature(p, name, loc, cps)
15302      char **p;
15303      char *name;
15304      int *loc;
15305      ChessProgramState *cps;
15306 {
15307   char buf[MSG_SIZ];
15308   int len = strlen(name);
15309   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15310     (*p) += len + 1;
15311     sscanf(*p, "%d", loc);
15312     while (**p && **p != ' ') (*p)++;
15313     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15314     SendToProgram(buf, cps);
15315     return TRUE;
15316   }
15317   return FALSE;
15318 }
15319
15320 int
15321 StringFeature(p, name, loc, cps)
15322      char **p;
15323      char *name;
15324      char loc[];
15325      ChessProgramState *cps;
15326 {
15327   char buf[MSG_SIZ];
15328   int len = strlen(name);
15329   if (strncmp((*p), name, len) == 0
15330       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15331     (*p) += len + 2;
15332     sscanf(*p, "%[^\"]", loc);
15333     while (**p && **p != '\"') (*p)++;
15334     if (**p == '\"') (*p)++;
15335     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15336     SendToProgram(buf, cps);
15337     return TRUE;
15338   }
15339   return FALSE;
15340 }
15341
15342 int
15343 ParseOption(Option *opt, ChessProgramState *cps)
15344 // [HGM] options: process the string that defines an engine option, and determine
15345 // name, type, default value, and allowed value range
15346 {
15347         char *p, *q, buf[MSG_SIZ];
15348         int n, min = (-1)<<31, max = 1<<31, def;
15349
15350         if(p = strstr(opt->name, " -spin ")) {
15351             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15352             if(max < min) max = min; // enforce consistency
15353             if(def < min) def = min;
15354             if(def > max) def = max;
15355             opt->value = def;
15356             opt->min = min;
15357             opt->max = max;
15358             opt->type = Spin;
15359         } else if((p = strstr(opt->name, " -slider "))) {
15360             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15361             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15362             if(max < min) max = min; // enforce consistency
15363             if(def < min) def = min;
15364             if(def > max) def = max;
15365             opt->value = def;
15366             opt->min = min;
15367             opt->max = max;
15368             opt->type = Spin; // Slider;
15369         } else if((p = strstr(opt->name, " -string "))) {
15370             opt->textValue = p+9;
15371             opt->type = TextBox;
15372         } else if((p = strstr(opt->name, " -file "))) {
15373             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15374             opt->textValue = p+7;
15375             opt->type = FileName; // FileName;
15376         } else if((p = strstr(opt->name, " -path "))) {
15377             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15378             opt->textValue = p+7;
15379             opt->type = PathName; // PathName;
15380         } else if(p = strstr(opt->name, " -check ")) {
15381             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15382             opt->value = (def != 0);
15383             opt->type = CheckBox;
15384         } else if(p = strstr(opt->name, " -combo ")) {
15385             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15386             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15387             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15388             opt->value = n = 0;
15389             while(q = StrStr(q, " /// ")) {
15390                 n++; *q = 0;    // count choices, and null-terminate each of them
15391                 q += 5;
15392                 if(*q == '*') { // remember default, which is marked with * prefix
15393                     q++;
15394                     opt->value = n;
15395                 }
15396                 cps->comboList[cps->comboCnt++] = q;
15397             }
15398             cps->comboList[cps->comboCnt++] = NULL;
15399             opt->max = n + 1;
15400             opt->type = ComboBox;
15401         } else if(p = strstr(opt->name, " -button")) {
15402             opt->type = Button;
15403         } else if(p = strstr(opt->name, " -save")) {
15404             opt->type = SaveButton;
15405         } else return FALSE;
15406         *p = 0; // terminate option name
15407         // now look if the command-line options define a setting for this engine option.
15408         if(cps->optionSettings && cps->optionSettings[0])
15409             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15410         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15411           snprintf(buf, MSG_SIZ, "option %s", p);
15412                 if(p = strstr(buf, ",")) *p = 0;
15413                 if(q = strchr(buf, '=')) switch(opt->type) {
15414                     case ComboBox:
15415                         for(n=0; n<opt->max; n++)
15416                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15417                         break;
15418                     case TextBox:
15419                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15420                         break;
15421                     case Spin:
15422                     case CheckBox:
15423                         opt->value = atoi(q+1);
15424                     default:
15425                         break;
15426                 }
15427                 strcat(buf, "\n");
15428                 SendToProgram(buf, cps);
15429         }
15430         return TRUE;
15431 }
15432
15433 void
15434 FeatureDone(cps, val)
15435      ChessProgramState* cps;
15436      int val;
15437 {
15438   DelayedEventCallback cb = GetDelayedEvent();
15439   if ((cb == InitBackEnd3 && cps == &first) ||
15440       (cb == SettingsMenuIfReady && cps == &second) ||
15441       (cb == LoadEngine) ||
15442       (cb == TwoMachinesEventIfReady)) {
15443     CancelDelayedEvent();
15444     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15445   }
15446   cps->initDone = val;
15447 }
15448
15449 /* Parse feature command from engine */
15450 void
15451 ParseFeatures(args, cps)
15452      char* args;
15453      ChessProgramState *cps;
15454 {
15455   char *p = args;
15456   char *q;
15457   int val;
15458   char buf[MSG_SIZ];
15459
15460   for (;;) {
15461     while (*p == ' ') p++;
15462     if (*p == NULLCHAR) return;
15463
15464     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15465     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15466     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15467     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15468     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15469     if (BoolFeature(&p, "reuse", &val, cps)) {
15470       /* Engine can disable reuse, but can't enable it if user said no */
15471       if (!val) cps->reuse = FALSE;
15472       continue;
15473     }
15474     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15475     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15476       if (gameMode == TwoMachinesPlay) {
15477         DisplayTwoMachinesTitle();
15478       } else {
15479         DisplayTitle("");
15480       }
15481       continue;
15482     }
15483     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15484     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15485     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15486     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15487     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15488     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15489     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15490     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15491     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15492     if (IntFeature(&p, "done", &val, cps)) {
15493       FeatureDone(cps, val);
15494       continue;
15495     }
15496     /* Added by Tord: */
15497     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15498     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15499     /* End of additions by Tord */
15500
15501     /* [HGM] added features: */
15502     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15503     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15504     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15505     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15506     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15507     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15508     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15509         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15510           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15511             SendToProgram(buf, cps);
15512             continue;
15513         }
15514         if(cps->nrOptions >= MAX_OPTIONS) {
15515             cps->nrOptions--;
15516             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15517             DisplayError(buf, 0);
15518         }
15519         continue;
15520     }
15521     /* End of additions by HGM */
15522
15523     /* unknown feature: complain and skip */
15524     q = p;
15525     while (*q && *q != '=') q++;
15526     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15527     SendToProgram(buf, cps);
15528     p = q;
15529     if (*p == '=') {
15530       p++;
15531       if (*p == '\"') {
15532         p++;
15533         while (*p && *p != '\"') p++;
15534         if (*p == '\"') p++;
15535       } else {
15536         while (*p && *p != ' ') p++;
15537       }
15538     }
15539   }
15540
15541 }
15542
15543 void
15544 PeriodicUpdatesEvent(newState)
15545      int newState;
15546 {
15547     if (newState == appData.periodicUpdates)
15548       return;
15549
15550     appData.periodicUpdates=newState;
15551
15552     /* Display type changes, so update it now */
15553 //    DisplayAnalysis();
15554
15555     /* Get the ball rolling again... */
15556     if (newState) {
15557         AnalysisPeriodicEvent(1);
15558         StartAnalysisClock();
15559     }
15560 }
15561
15562 void
15563 PonderNextMoveEvent(newState)
15564      int newState;
15565 {
15566     if (newState == appData.ponderNextMove) return;
15567     if (gameMode == EditPosition) EditPositionDone(TRUE);
15568     if (newState) {
15569         SendToProgram("hard\n", &first);
15570         if (gameMode == TwoMachinesPlay) {
15571             SendToProgram("hard\n", &second);
15572         }
15573     } else {
15574         SendToProgram("easy\n", &first);
15575         thinkOutput[0] = NULLCHAR;
15576         if (gameMode == TwoMachinesPlay) {
15577             SendToProgram("easy\n", &second);
15578         }
15579     }
15580     appData.ponderNextMove = newState;
15581 }
15582
15583 void
15584 NewSettingEvent(option, feature, command, value)
15585      char *command;
15586      int option, value, *feature;
15587 {
15588     char buf[MSG_SIZ];
15589
15590     if (gameMode == EditPosition) EditPositionDone(TRUE);
15591     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15592     if(feature == NULL || *feature) SendToProgram(buf, &first);
15593     if (gameMode == TwoMachinesPlay) {
15594         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15595     }
15596 }
15597
15598 void
15599 ShowThinkingEvent()
15600 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15601 {
15602     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15603     int newState = appData.showThinking
15604         // [HGM] thinking: other features now need thinking output as well
15605         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15606
15607     if (oldState == newState) return;
15608     oldState = newState;
15609     if (gameMode == EditPosition) EditPositionDone(TRUE);
15610     if (oldState) {
15611         SendToProgram("post\n", &first);
15612         if (gameMode == TwoMachinesPlay) {
15613             SendToProgram("post\n", &second);
15614         }
15615     } else {
15616         SendToProgram("nopost\n", &first);
15617         thinkOutput[0] = NULLCHAR;
15618         if (gameMode == TwoMachinesPlay) {
15619             SendToProgram("nopost\n", &second);
15620         }
15621     }
15622 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15623 }
15624
15625 void
15626 AskQuestionEvent(title, question, replyPrefix, which)
15627      char *title; char *question; char *replyPrefix; char *which;
15628 {
15629   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15630   if (pr == NoProc) return;
15631   AskQuestion(title, question, replyPrefix, pr);
15632 }
15633
15634 void
15635 TypeInEvent(char firstChar)
15636 {
15637     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15638         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15639         gameMode == AnalyzeMode || gameMode == EditGame || 
15640         gameMode == EditPosition || gameMode == IcsExamining ||
15641         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15642         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15643                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15644                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15645         gameMode == Training) PopUpMoveDialog(firstChar);
15646 }
15647
15648 void
15649 TypeInDoneEvent(char *move)
15650 {
15651         Board board;
15652         int n, fromX, fromY, toX, toY;
15653         char promoChar;
15654         ChessMove moveType;
15655
15656         // [HGM] FENedit
15657         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15658                 EditPositionPasteFEN(move);
15659                 return;
15660         }
15661         // [HGM] movenum: allow move number to be typed in any mode
15662         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15663           ToNrEvent(2*n-1);
15664           return;
15665         }
15666
15667       if (gameMode != EditGame && currentMove != forwardMostMove && 
15668         gameMode != Training) {
15669         DisplayMoveError(_("Displayed move is not current"));
15670       } else {
15671         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15672           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15673         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15674         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15675           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15676           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15677         } else {
15678           DisplayMoveError(_("Could not parse move"));
15679         }
15680       }
15681 }
15682
15683 void
15684 DisplayMove(moveNumber)
15685      int moveNumber;
15686 {
15687     char message[MSG_SIZ];
15688     char res[MSG_SIZ];
15689     char cpThinkOutput[MSG_SIZ];
15690
15691     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15692
15693     if (moveNumber == forwardMostMove - 1 ||
15694         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15695
15696         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15697
15698         if (strchr(cpThinkOutput, '\n')) {
15699             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15700         }
15701     } else {
15702         *cpThinkOutput = NULLCHAR;
15703     }
15704
15705     /* [AS] Hide thinking from human user */
15706     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15707         *cpThinkOutput = NULLCHAR;
15708         if( thinkOutput[0] != NULLCHAR ) {
15709             int i;
15710
15711             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15712                 cpThinkOutput[i] = '.';
15713             }
15714             cpThinkOutput[i] = NULLCHAR;
15715             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15716         }
15717     }
15718
15719     if (moveNumber == forwardMostMove - 1 &&
15720         gameInfo.resultDetails != NULL) {
15721         if (gameInfo.resultDetails[0] == NULLCHAR) {
15722           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15723         } else {
15724           snprintf(res, MSG_SIZ, " {%s} %s",
15725                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15726         }
15727     } else {
15728         res[0] = NULLCHAR;
15729     }
15730
15731     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15732         DisplayMessage(res, cpThinkOutput);
15733     } else {
15734       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15735                 WhiteOnMove(moveNumber) ? " " : ".. ",
15736                 parseList[moveNumber], res);
15737         DisplayMessage(message, cpThinkOutput);
15738     }
15739 }
15740
15741 void
15742 DisplayComment(moveNumber, text)
15743      int moveNumber;
15744      char *text;
15745 {
15746     char title[MSG_SIZ];
15747
15748     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15749       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15750     } else {
15751       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15752               WhiteOnMove(moveNumber) ? " " : ".. ",
15753               parseList[moveNumber]);
15754     }
15755     if (text != NULL && (appData.autoDisplayComment || commentUp))
15756         CommentPopUp(title, text);
15757 }
15758
15759 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15760  * might be busy thinking or pondering.  It can be omitted if your
15761  * gnuchess is configured to stop thinking immediately on any user
15762  * input.  However, that gnuchess feature depends on the FIONREAD
15763  * ioctl, which does not work properly on some flavors of Unix.
15764  */
15765 void
15766 Attention(cps)
15767      ChessProgramState *cps;
15768 {
15769 #if ATTENTION
15770     if (!cps->useSigint) return;
15771     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15772     switch (gameMode) {
15773       case MachinePlaysWhite:
15774       case MachinePlaysBlack:
15775       case TwoMachinesPlay:
15776       case IcsPlayingWhite:
15777       case IcsPlayingBlack:
15778       case AnalyzeMode:
15779       case AnalyzeFile:
15780         /* Skip if we know it isn't thinking */
15781         if (!cps->maybeThinking) return;
15782         if (appData.debugMode)
15783           fprintf(debugFP, "Interrupting %s\n", cps->which);
15784         InterruptChildProcess(cps->pr);
15785         cps->maybeThinking = FALSE;
15786         break;
15787       default:
15788         break;
15789     }
15790 #endif /*ATTENTION*/
15791 }
15792
15793 int
15794 CheckFlags()
15795 {
15796     if (whiteTimeRemaining <= 0) {
15797         if (!whiteFlag) {
15798             whiteFlag = TRUE;
15799             if (appData.icsActive) {
15800                 if (appData.autoCallFlag &&
15801                     gameMode == IcsPlayingBlack && !blackFlag) {
15802                   SendToICS(ics_prefix);
15803                   SendToICS("flag\n");
15804                 }
15805             } else {
15806                 if (blackFlag) {
15807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15808                 } else {
15809                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15810                     if (appData.autoCallFlag) {
15811                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15812                         return TRUE;
15813                     }
15814                 }
15815             }
15816         }
15817     }
15818     if (blackTimeRemaining <= 0) {
15819         if (!blackFlag) {
15820             blackFlag = TRUE;
15821             if (appData.icsActive) {
15822                 if (appData.autoCallFlag &&
15823                     gameMode == IcsPlayingWhite && !whiteFlag) {
15824                   SendToICS(ics_prefix);
15825                   SendToICS("flag\n");
15826                 }
15827             } else {
15828                 if (whiteFlag) {
15829                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15830                 } else {
15831                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15832                     if (appData.autoCallFlag) {
15833                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15834                         return TRUE;
15835                     }
15836                 }
15837             }
15838         }
15839     }
15840     return FALSE;
15841 }
15842
15843 void
15844 CheckTimeControl()
15845 {
15846     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15847         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15848
15849     /*
15850      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15851      */
15852     if ( !WhiteOnMove(forwardMostMove) ) {
15853         /* White made time control */
15854         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15855         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15856         /* [HGM] time odds: correct new time quota for time odds! */
15857                                             / WhitePlayer()->timeOdds;
15858         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15859     } else {
15860         lastBlack -= blackTimeRemaining;
15861         /* Black made time control */
15862         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15863                                             / WhitePlayer()->other->timeOdds;
15864         lastWhite = whiteTimeRemaining;
15865     }
15866 }
15867
15868 void
15869 DisplayBothClocks()
15870 {
15871     int wom = gameMode == EditPosition ?
15872       !blackPlaysFirst : WhiteOnMove(currentMove);
15873     DisplayWhiteClock(whiteTimeRemaining, wom);
15874     DisplayBlackClock(blackTimeRemaining, !wom);
15875 }
15876
15877
15878 /* Timekeeping seems to be a portability nightmare.  I think everyone
15879    has ftime(), but I'm really not sure, so I'm including some ifdefs
15880    to use other calls if you don't.  Clocks will be less accurate if
15881    you have neither ftime nor gettimeofday.
15882 */
15883
15884 /* VS 2008 requires the #include outside of the function */
15885 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15886 #include <sys/timeb.h>
15887 #endif
15888
15889 /* Get the current time as a TimeMark */
15890 void
15891 GetTimeMark(tm)
15892      TimeMark *tm;
15893 {
15894 #if HAVE_GETTIMEOFDAY
15895
15896     struct timeval timeVal;
15897     struct timezone timeZone;
15898
15899     gettimeofday(&timeVal, &timeZone);
15900     tm->sec = (long) timeVal.tv_sec;
15901     tm->ms = (int) (timeVal.tv_usec / 1000L);
15902
15903 #else /*!HAVE_GETTIMEOFDAY*/
15904 #if HAVE_FTIME
15905
15906 // include <sys/timeb.h> / moved to just above start of function
15907     struct timeb timeB;
15908
15909     ftime(&timeB);
15910     tm->sec = (long) timeB.time;
15911     tm->ms = (int) timeB.millitm;
15912
15913 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15914     tm->sec = (long) time(NULL);
15915     tm->ms = 0;
15916 #endif
15917 #endif
15918 }
15919
15920 /* Return the difference in milliseconds between two
15921    time marks.  We assume the difference will fit in a long!
15922 */
15923 long
15924 SubtractTimeMarks(tm2, tm1)
15925      TimeMark *tm2, *tm1;
15926 {
15927     return 1000L*(tm2->sec - tm1->sec) +
15928            (long) (tm2->ms - tm1->ms);
15929 }
15930
15931
15932 /*
15933  * Code to manage the game clocks.
15934  *
15935  * In tournament play, black starts the clock and then white makes a move.
15936  * We give the human user a slight advantage if he is playing white---the
15937  * clocks don't run until he makes his first move, so it takes zero time.
15938  * Also, we don't account for network lag, so we could get out of sync
15939  * with GNU Chess's clock -- but then, referees are always right.
15940  */
15941
15942 static TimeMark tickStartTM;
15943 static long intendedTickLength;
15944
15945 long
15946 NextTickLength(timeRemaining)
15947      long timeRemaining;
15948 {
15949     long nominalTickLength, nextTickLength;
15950
15951     if (timeRemaining > 0L && timeRemaining <= 10000L)
15952       nominalTickLength = 100L;
15953     else
15954       nominalTickLength = 1000L;
15955     nextTickLength = timeRemaining % nominalTickLength;
15956     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15957
15958     return nextTickLength;
15959 }
15960
15961 /* Adjust clock one minute up or down */
15962 void
15963 AdjustClock(Boolean which, int dir)
15964 {
15965     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15966     if(which) blackTimeRemaining += 60000*dir;
15967     else      whiteTimeRemaining += 60000*dir;
15968     DisplayBothClocks();
15969     adjustedClock = TRUE;
15970 }
15971
15972 /* Stop clocks and reset to a fresh time control */
15973 void
15974 ResetClocks()
15975 {
15976     (void) StopClockTimer();
15977     if (appData.icsActive) {
15978         whiteTimeRemaining = blackTimeRemaining = 0;
15979     } else if (searchTime) {
15980         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15981         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15982     } else { /* [HGM] correct new time quote for time odds */
15983         whiteTC = blackTC = fullTimeControlString;
15984         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15985         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15986     }
15987     if (whiteFlag || blackFlag) {
15988         DisplayTitle("");
15989         whiteFlag = blackFlag = FALSE;
15990     }
15991     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15992     DisplayBothClocks();
15993     adjustedClock = FALSE;
15994 }
15995
15996 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15997
15998 /* Decrement running clock by amount of time that has passed */
15999 void
16000 DecrementClocks()
16001 {
16002     long timeRemaining;
16003     long lastTickLength, fudge;
16004     TimeMark now;
16005
16006     if (!appData.clockMode) return;
16007     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16008
16009     GetTimeMark(&now);
16010
16011     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16012
16013     /* Fudge if we woke up a little too soon */
16014     fudge = intendedTickLength - lastTickLength;
16015     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16016
16017     if (WhiteOnMove(forwardMostMove)) {
16018         if(whiteNPS >= 0) lastTickLength = 0;
16019         timeRemaining = whiteTimeRemaining -= lastTickLength;
16020         if(timeRemaining < 0 && !appData.icsActive) {
16021             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16022             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16023                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16024                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16025             }
16026         }
16027         DisplayWhiteClock(whiteTimeRemaining - fudge,
16028                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16029     } else {
16030         if(blackNPS >= 0) lastTickLength = 0;
16031         timeRemaining = blackTimeRemaining -= lastTickLength;
16032         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16033             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16034             if(suddenDeath) {
16035                 blackStartMove = forwardMostMove;
16036                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16037             }
16038         }
16039         DisplayBlackClock(blackTimeRemaining - fudge,
16040                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16041     }
16042     if (CheckFlags()) return;
16043
16044     tickStartTM = now;
16045     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16046     StartClockTimer(intendedTickLength);
16047
16048     /* if the time remaining has fallen below the alarm threshold, sound the
16049      * alarm. if the alarm has sounded and (due to a takeback or time control
16050      * with increment) the time remaining has increased to a level above the
16051      * threshold, reset the alarm so it can sound again.
16052      */
16053
16054     if (appData.icsActive && appData.icsAlarm) {
16055
16056         /* make sure we are dealing with the user's clock */
16057         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16058                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16059            )) return;
16060
16061         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16062             alarmSounded = FALSE;
16063         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16064             PlayAlarmSound();
16065             alarmSounded = TRUE;
16066         }
16067     }
16068 }
16069
16070
16071 /* A player has just moved, so stop the previously running
16072    clock and (if in clock mode) start the other one.
16073    We redisplay both clocks in case we're in ICS mode, because
16074    ICS gives us an update to both clocks after every move.
16075    Note that this routine is called *after* forwardMostMove
16076    is updated, so the last fractional tick must be subtracted
16077    from the color that is *not* on move now.
16078 */
16079 void
16080 SwitchClocks(int newMoveNr)
16081 {
16082     long lastTickLength;
16083     TimeMark now;
16084     int flagged = FALSE;
16085
16086     GetTimeMark(&now);
16087
16088     if (StopClockTimer() && appData.clockMode) {
16089         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16090         if (!WhiteOnMove(forwardMostMove)) {
16091             if(blackNPS >= 0) lastTickLength = 0;
16092             blackTimeRemaining -= lastTickLength;
16093            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16094 //         if(pvInfoList[forwardMostMove].time == -1)
16095                  pvInfoList[forwardMostMove].time =               // use GUI time
16096                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16097         } else {
16098            if(whiteNPS >= 0) lastTickLength = 0;
16099            whiteTimeRemaining -= lastTickLength;
16100            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16101 //         if(pvInfoList[forwardMostMove].time == -1)
16102                  pvInfoList[forwardMostMove].time =
16103                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16104         }
16105         flagged = CheckFlags();
16106     }
16107     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16108     CheckTimeControl();
16109
16110     if (flagged || !appData.clockMode) return;
16111
16112     switch (gameMode) {
16113       case MachinePlaysBlack:
16114       case MachinePlaysWhite:
16115       case BeginningOfGame:
16116         if (pausing) return;
16117         break;
16118
16119       case EditGame:
16120       case PlayFromGameFile:
16121       case IcsExamining:
16122         return;
16123
16124       default:
16125         break;
16126     }
16127
16128     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16129         if(WhiteOnMove(forwardMostMove))
16130              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16131         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16132     }
16133
16134     tickStartTM = now;
16135     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16136       whiteTimeRemaining : blackTimeRemaining);
16137     StartClockTimer(intendedTickLength);
16138 }
16139
16140
16141 /* Stop both clocks */
16142 void
16143 StopClocks()
16144 {
16145     long lastTickLength;
16146     TimeMark now;
16147
16148     if (!StopClockTimer()) return;
16149     if (!appData.clockMode) return;
16150
16151     GetTimeMark(&now);
16152
16153     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16154     if (WhiteOnMove(forwardMostMove)) {
16155         if(whiteNPS >= 0) lastTickLength = 0;
16156         whiteTimeRemaining -= lastTickLength;
16157         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16158     } else {
16159         if(blackNPS >= 0) lastTickLength = 0;
16160         blackTimeRemaining -= lastTickLength;
16161         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16162     }
16163     CheckFlags();
16164 }
16165
16166 /* Start clock of player on move.  Time may have been reset, so
16167    if clock is already running, stop and restart it. */
16168 void
16169 StartClocks()
16170 {
16171     (void) StopClockTimer(); /* in case it was running already */
16172     DisplayBothClocks();
16173     if (CheckFlags()) return;
16174
16175     if (!appData.clockMode) return;
16176     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16177
16178     GetTimeMark(&tickStartTM);
16179     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16180       whiteTimeRemaining : blackTimeRemaining);
16181
16182    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16183     whiteNPS = blackNPS = -1;
16184     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16185        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16186         whiteNPS = first.nps;
16187     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16188        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16189         blackNPS = first.nps;
16190     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16191         whiteNPS = second.nps;
16192     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16193         blackNPS = second.nps;
16194     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16195
16196     StartClockTimer(intendedTickLength);
16197 }
16198
16199 char *
16200 TimeString(ms)
16201      long ms;
16202 {
16203     long second, minute, hour, day;
16204     char *sign = "";
16205     static char buf[32];
16206
16207     if (ms > 0 && ms <= 9900) {
16208       /* convert milliseconds to tenths, rounding up */
16209       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16210
16211       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16212       return buf;
16213     }
16214
16215     /* convert milliseconds to seconds, rounding up */
16216     /* use floating point to avoid strangeness of integer division
16217        with negative dividends on many machines */
16218     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16219
16220     if (second < 0) {
16221         sign = "-";
16222         second = -second;
16223     }
16224
16225     day = second / (60 * 60 * 24);
16226     second = second % (60 * 60 * 24);
16227     hour = second / (60 * 60);
16228     second = second % (60 * 60);
16229     minute = second / 60;
16230     second = second % 60;
16231
16232     if (day > 0)
16233       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16234               sign, day, hour, minute, second);
16235     else if (hour > 0)
16236       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16237     else
16238       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16239
16240     return buf;
16241 }
16242
16243
16244 /*
16245  * This is necessary because some C libraries aren't ANSI C compliant yet.
16246  */
16247 char *
16248 StrStr(string, match)
16249      char *string, *match;
16250 {
16251     int i, length;
16252
16253     length = strlen(match);
16254
16255     for (i = strlen(string) - length; i >= 0; i--, string++)
16256       if (!strncmp(match, string, length))
16257         return string;
16258
16259     return NULL;
16260 }
16261
16262 char *
16263 StrCaseStr(string, match)
16264      char *string, *match;
16265 {
16266     int i, j, length;
16267
16268     length = strlen(match);
16269
16270     for (i = strlen(string) - length; i >= 0; i--, string++) {
16271         for (j = 0; j < length; j++) {
16272             if (ToLower(match[j]) != ToLower(string[j]))
16273               break;
16274         }
16275         if (j == length) return string;
16276     }
16277
16278     return NULL;
16279 }
16280
16281 #ifndef _amigados
16282 int
16283 StrCaseCmp(s1, s2)
16284      char *s1, *s2;
16285 {
16286     char c1, c2;
16287
16288     for (;;) {
16289         c1 = ToLower(*s1++);
16290         c2 = ToLower(*s2++);
16291         if (c1 > c2) return 1;
16292         if (c1 < c2) return -1;
16293         if (c1 == NULLCHAR) return 0;
16294     }
16295 }
16296
16297
16298 int
16299 ToLower(c)
16300      int c;
16301 {
16302     return isupper(c) ? tolower(c) : c;
16303 }
16304
16305
16306 int
16307 ToUpper(c)
16308      int c;
16309 {
16310     return islower(c) ? toupper(c) : c;
16311 }
16312 #endif /* !_amigados    */
16313
16314 char *
16315 StrSave(s)
16316      char *s;
16317 {
16318   char *ret;
16319
16320   if ((ret = (char *) malloc(strlen(s) + 1)))
16321     {
16322       safeStrCpy(ret, s, strlen(s)+1);
16323     }
16324   return ret;
16325 }
16326
16327 char *
16328 StrSavePtr(s, savePtr)
16329      char *s, **savePtr;
16330 {
16331     if (*savePtr) {
16332         free(*savePtr);
16333     }
16334     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16335       safeStrCpy(*savePtr, s, strlen(s)+1);
16336     }
16337     return(*savePtr);
16338 }
16339
16340 char *
16341 PGNDate()
16342 {
16343     time_t clock;
16344     struct tm *tm;
16345     char buf[MSG_SIZ];
16346
16347     clock = time((time_t *)NULL);
16348     tm = localtime(&clock);
16349     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16350             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16351     return StrSave(buf);
16352 }
16353
16354
16355 char *
16356 PositionToFEN(move, overrideCastling)
16357      int move;
16358      char *overrideCastling;
16359 {
16360     int i, j, fromX, fromY, toX, toY;
16361     int whiteToPlay;
16362     char buf[MSG_SIZ];
16363     char *p, *q;
16364     int emptycount;
16365     ChessSquare piece;
16366
16367     whiteToPlay = (gameMode == EditPosition) ?
16368       !blackPlaysFirst : (move % 2 == 0);
16369     p = buf;
16370
16371     /* Piece placement data */
16372     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16373         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16374         emptycount = 0;
16375         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16376             if (boards[move][i][j] == EmptySquare) {
16377                 emptycount++;
16378             } else { ChessSquare piece = boards[move][i][j];
16379                 if (emptycount > 0) {
16380                     if(emptycount<10) /* [HGM] can be >= 10 */
16381                         *p++ = '0' + emptycount;
16382                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16383                     emptycount = 0;
16384                 }
16385                 if(PieceToChar(piece) == '+') {
16386                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16387                     *p++ = '+';
16388                     piece = (ChessSquare)(DEMOTED piece);
16389                 }
16390                 *p++ = PieceToChar(piece);
16391                 if(p[-1] == '~') {
16392                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16393                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16394                     *p++ = '~';
16395                 }
16396             }
16397         }
16398         if (emptycount > 0) {
16399             if(emptycount<10) /* [HGM] can be >= 10 */
16400                 *p++ = '0' + emptycount;
16401             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16402             emptycount = 0;
16403         }
16404         *p++ = '/';
16405     }
16406     *(p - 1) = ' ';
16407
16408     /* [HGM] print Crazyhouse or Shogi holdings */
16409     if( gameInfo.holdingsWidth ) {
16410         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16411         q = p;
16412         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16413             piece = boards[move][i][BOARD_WIDTH-1];
16414             if( piece != EmptySquare )
16415               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16416                   *p++ = PieceToChar(piece);
16417         }
16418         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16419             piece = boards[move][BOARD_HEIGHT-i-1][0];
16420             if( piece != EmptySquare )
16421               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16422                   *p++ = PieceToChar(piece);
16423         }
16424
16425         if( q == p ) *p++ = '-';
16426         *p++ = ']';
16427         *p++ = ' ';
16428     }
16429
16430     /* Active color */
16431     *p++ = whiteToPlay ? 'w' : 'b';
16432     *p++ = ' ';
16433
16434   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16435     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16436   } else {
16437   if(nrCastlingRights) {
16438      q = p;
16439      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16440        /* [HGM] write directly from rights */
16441            if(boards[move][CASTLING][2] != NoRights &&
16442               boards[move][CASTLING][0] != NoRights   )
16443                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16444            if(boards[move][CASTLING][2] != NoRights &&
16445               boards[move][CASTLING][1] != NoRights   )
16446                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16447            if(boards[move][CASTLING][5] != NoRights &&
16448               boards[move][CASTLING][3] != NoRights   )
16449                 *p++ = boards[move][CASTLING][3] + AAA;
16450            if(boards[move][CASTLING][5] != NoRights &&
16451               boards[move][CASTLING][4] != NoRights   )
16452                 *p++ = boards[move][CASTLING][4] + AAA;
16453      } else {
16454
16455         /* [HGM] write true castling rights */
16456         if( nrCastlingRights == 6 ) {
16457             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16458                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16459             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16460                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16461             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16462                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16463             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16464                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16465         }
16466      }
16467      if (q == p) *p++ = '-'; /* No castling rights */
16468      *p++ = ' ';
16469   }
16470
16471   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16472      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16473     /* En passant target square */
16474     if (move > backwardMostMove) {
16475         fromX = moveList[move - 1][0] - AAA;
16476         fromY = moveList[move - 1][1] - ONE;
16477         toX = moveList[move - 1][2] - AAA;
16478         toY = moveList[move - 1][3] - ONE;
16479         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16480             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16481             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16482             fromX == toX) {
16483             /* 2-square pawn move just happened */
16484             *p++ = toX + AAA;
16485             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16486         } else {
16487             *p++ = '-';
16488         }
16489     } else if(move == backwardMostMove) {
16490         // [HGM] perhaps we should always do it like this, and forget the above?
16491         if((signed char)boards[move][EP_STATUS] >= 0) {
16492             *p++ = boards[move][EP_STATUS] + AAA;
16493             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16494         } else {
16495             *p++ = '-';
16496         }
16497     } else {
16498         *p++ = '-';
16499     }
16500     *p++ = ' ';
16501   }
16502   }
16503
16504     /* [HGM] find reversible plies */
16505     {   int i = 0, j=move;
16506
16507         if (appData.debugMode) { int k;
16508             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16509             for(k=backwardMostMove; k<=forwardMostMove; k++)
16510                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16511
16512         }
16513
16514         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16515         if( j == backwardMostMove ) i += initialRulePlies;
16516         sprintf(p, "%d ", i);
16517         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16518     }
16519     /* Fullmove number */
16520     sprintf(p, "%d", (move / 2) + 1);
16521
16522     return StrSave(buf);
16523 }
16524
16525 Boolean
16526 ParseFEN(board, blackPlaysFirst, fen)
16527     Board board;
16528      int *blackPlaysFirst;
16529      char *fen;
16530 {
16531     int i, j;
16532     char *p, c;
16533     int emptycount;
16534     ChessSquare piece;
16535
16536     p = fen;
16537
16538     /* [HGM] by default clear Crazyhouse holdings, if present */
16539     if(gameInfo.holdingsWidth) {
16540        for(i=0; i<BOARD_HEIGHT; i++) {
16541            board[i][0]             = EmptySquare; /* black holdings */
16542            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16543            board[i][1]             = (ChessSquare) 0; /* black counts */
16544            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16545        }
16546     }
16547
16548     /* Piece placement data */
16549     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16550         j = 0;
16551         for (;;) {
16552             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16553                 if (*p == '/') p++;
16554                 emptycount = gameInfo.boardWidth - j;
16555                 while (emptycount--)
16556                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16557                 break;
16558 #if(BOARD_FILES >= 10)
16559             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16560                 p++; emptycount=10;
16561                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16562                 while (emptycount--)
16563                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16564 #endif
16565             } else if (isdigit(*p)) {
16566                 emptycount = *p++ - '0';
16567                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16568                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16569                 while (emptycount--)
16570                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16571             } else if (*p == '+' || isalpha(*p)) {
16572                 if (j >= gameInfo.boardWidth) return FALSE;
16573                 if(*p=='+') {
16574                     piece = CharToPiece(*++p);
16575                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16576                     piece = (ChessSquare) (PROMOTED piece ); p++;
16577                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16578                 } else piece = CharToPiece(*p++);
16579
16580                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16581                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16582                     piece = (ChessSquare) (PROMOTED piece);
16583                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16584                     p++;
16585                 }
16586                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16587             } else {
16588                 return FALSE;
16589             }
16590         }
16591     }
16592     while (*p == '/' || *p == ' ') p++;
16593
16594     /* [HGM] look for Crazyhouse holdings here */
16595     while(*p==' ') p++;
16596     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16597         if(*p == '[') p++;
16598         if(*p == '-' ) p++; /* empty holdings */ else {
16599             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16600             /* if we would allow FEN reading to set board size, we would   */
16601             /* have to add holdings and shift the board read so far here   */
16602             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16603                 p++;
16604                 if((int) piece >= (int) BlackPawn ) {
16605                     i = (int)piece - (int)BlackPawn;
16606                     i = PieceToNumber((ChessSquare)i);
16607                     if( i >= gameInfo.holdingsSize ) return FALSE;
16608                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16609                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16610                 } else {
16611                     i = (int)piece - (int)WhitePawn;
16612                     i = PieceToNumber((ChessSquare)i);
16613                     if( i >= gameInfo.holdingsSize ) return FALSE;
16614                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16615                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16616                 }
16617             }
16618         }
16619         if(*p == ']') p++;
16620     }
16621
16622     while(*p == ' ') p++;
16623
16624     /* Active color */
16625     c = *p++;
16626     if(appData.colorNickNames) {
16627       if( c == appData.colorNickNames[0] ) c = 'w'; else
16628       if( c == appData.colorNickNames[1] ) c = 'b';
16629     }
16630     switch (c) {
16631       case 'w':
16632         *blackPlaysFirst = FALSE;
16633         break;
16634       case 'b':
16635         *blackPlaysFirst = TRUE;
16636         break;
16637       default:
16638         return FALSE;
16639     }
16640
16641     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16642     /* return the extra info in global variiables             */
16643
16644     /* set defaults in case FEN is incomplete */
16645     board[EP_STATUS] = EP_UNKNOWN;
16646     for(i=0; i<nrCastlingRights; i++ ) {
16647         board[CASTLING][i] =
16648             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16649     }   /* assume possible unless obviously impossible */
16650     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16651     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16652     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16653                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16654     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16655     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16656     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16657                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16658     FENrulePlies = 0;
16659
16660     while(*p==' ') p++;
16661     if(nrCastlingRights) {
16662       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16663           /* castling indicator present, so default becomes no castlings */
16664           for(i=0; i<nrCastlingRights; i++ ) {
16665                  board[CASTLING][i] = NoRights;
16666           }
16667       }
16668       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16669              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16670              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16671              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16672         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16673
16674         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16675             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16676             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16677         }
16678         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16679             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16680         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16681                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16682         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16683                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16684         switch(c) {
16685           case'K':
16686               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16687               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16688               board[CASTLING][2] = whiteKingFile;
16689               break;
16690           case'Q':
16691               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16692               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16693               board[CASTLING][2] = whiteKingFile;
16694               break;
16695           case'k':
16696               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16697               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16698               board[CASTLING][5] = blackKingFile;
16699               break;
16700           case'q':
16701               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16702               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16703               board[CASTLING][5] = blackKingFile;
16704           case '-':
16705               break;
16706           default: /* FRC castlings */
16707               if(c >= 'a') { /* black rights */
16708                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16709                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16710                   if(i == BOARD_RGHT) break;
16711                   board[CASTLING][5] = i;
16712                   c -= AAA;
16713                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16714                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16715                   if(c > i)
16716                       board[CASTLING][3] = c;
16717                   else
16718                       board[CASTLING][4] = c;
16719               } else { /* white rights */
16720                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16721                     if(board[0][i] == WhiteKing) break;
16722                   if(i == BOARD_RGHT) break;
16723                   board[CASTLING][2] = i;
16724                   c -= AAA - 'a' + 'A';
16725                   if(board[0][c] >= WhiteKing) break;
16726                   if(c > i)
16727                       board[CASTLING][0] = c;
16728                   else
16729                       board[CASTLING][1] = c;
16730               }
16731         }
16732       }
16733       for(i=0; i<nrCastlingRights; i++)
16734         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16735     if (appData.debugMode) {
16736         fprintf(debugFP, "FEN castling rights:");
16737         for(i=0; i<nrCastlingRights; i++)
16738         fprintf(debugFP, " %d", board[CASTLING][i]);
16739         fprintf(debugFP, "\n");
16740     }
16741
16742       while(*p==' ') p++;
16743     }
16744
16745     /* read e.p. field in games that know e.p. capture */
16746     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16747        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16748       if(*p=='-') {
16749         p++; board[EP_STATUS] = EP_NONE;
16750       } else {
16751          char c = *p++ - AAA;
16752
16753          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16754          if(*p >= '0' && *p <='9') p++;
16755          board[EP_STATUS] = c;
16756       }
16757     }
16758
16759
16760     if(sscanf(p, "%d", &i) == 1) {
16761         FENrulePlies = i; /* 50-move ply counter */
16762         /* (The move number is still ignored)    */
16763     }
16764
16765     return TRUE;
16766 }
16767
16768 void
16769 EditPositionPasteFEN(char *fen)
16770 {
16771   if (fen != NULL) {
16772     Board initial_position;
16773
16774     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16775       DisplayError(_("Bad FEN position in clipboard"), 0);
16776       return ;
16777     } else {
16778       int savedBlackPlaysFirst = blackPlaysFirst;
16779       EditPositionEvent();
16780       blackPlaysFirst = savedBlackPlaysFirst;
16781       CopyBoard(boards[0], initial_position);
16782       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16783       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16784       DisplayBothClocks();
16785       DrawPosition(FALSE, boards[currentMove]);
16786     }
16787   }
16788 }
16789
16790 static char cseq[12] = "\\   ";
16791
16792 Boolean set_cont_sequence(char *new_seq)
16793 {
16794     int len;
16795     Boolean ret;
16796
16797     // handle bad attempts to set the sequence
16798         if (!new_seq)
16799                 return 0; // acceptable error - no debug
16800
16801     len = strlen(new_seq);
16802     ret = (len > 0) && (len < sizeof(cseq));
16803     if (ret)
16804       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16805     else if (appData.debugMode)
16806       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16807     return ret;
16808 }
16809
16810 /*
16811     reformat a source message so words don't cross the width boundary.  internal
16812     newlines are not removed.  returns the wrapped size (no null character unless
16813     included in source message).  If dest is NULL, only calculate the size required
16814     for the dest buffer.  lp argument indicats line position upon entry, and it's
16815     passed back upon exit.
16816 */
16817 int wrap(char *dest, char *src, int count, int width, int *lp)
16818 {
16819     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16820
16821     cseq_len = strlen(cseq);
16822     old_line = line = *lp;
16823     ansi = len = clen = 0;
16824
16825     for (i=0; i < count; i++)
16826     {
16827         if (src[i] == '\033')
16828             ansi = 1;
16829
16830         // if we hit the width, back up
16831         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16832         {
16833             // store i & len in case the word is too long
16834             old_i = i, old_len = len;
16835
16836             // find the end of the last word
16837             while (i && src[i] != ' ' && src[i] != '\n')
16838             {
16839                 i--;
16840                 len--;
16841             }
16842
16843             // word too long?  restore i & len before splitting it
16844             if ((old_i-i+clen) >= width)
16845             {
16846                 i = old_i;
16847                 len = old_len;
16848             }
16849
16850             // extra space?
16851             if (i && src[i-1] == ' ')
16852                 len--;
16853
16854             if (src[i] != ' ' && src[i] != '\n')
16855             {
16856                 i--;
16857                 if (len)
16858                     len--;
16859             }
16860
16861             // now append the newline and continuation sequence
16862             if (dest)
16863                 dest[len] = '\n';
16864             len++;
16865             if (dest)
16866                 strncpy(dest+len, cseq, cseq_len);
16867             len += cseq_len;
16868             line = cseq_len;
16869             clen = cseq_len;
16870             continue;
16871         }
16872
16873         if (dest)
16874             dest[len] = src[i];
16875         len++;
16876         if (!ansi)
16877             line++;
16878         if (src[i] == '\n')
16879             line = 0;
16880         if (src[i] == 'm')
16881             ansi = 0;
16882     }
16883     if (dest && appData.debugMode)
16884     {
16885         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16886             count, width, line, len, *lp);
16887         show_bytes(debugFP, src, count);
16888         fprintf(debugFP, "\ndest: ");
16889         show_bytes(debugFP, dest, len);
16890         fprintf(debugFP, "\n");
16891     }
16892     *lp = dest ? line : old_line;
16893
16894     return len;
16895 }
16896
16897 // [HGM] vari: routines for shelving variations
16898 Boolean modeRestore = FALSE;
16899
16900 void
16901 PushInner(int firstMove, int lastMove)
16902 {
16903         int i, j, nrMoves = lastMove - firstMove;
16904
16905         // push current tail of game on stack
16906         savedResult[storedGames] = gameInfo.result;
16907         savedDetails[storedGames] = gameInfo.resultDetails;
16908         gameInfo.resultDetails = NULL;
16909         savedFirst[storedGames] = firstMove;
16910         savedLast [storedGames] = lastMove;
16911         savedFramePtr[storedGames] = framePtr;
16912         framePtr -= nrMoves; // reserve space for the boards
16913         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16914             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16915             for(j=0; j<MOVE_LEN; j++)
16916                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16917             for(j=0; j<2*MOVE_LEN; j++)
16918                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16919             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16920             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16921             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16922             pvInfoList[firstMove+i-1].depth = 0;
16923             commentList[framePtr+i] = commentList[firstMove+i];
16924             commentList[firstMove+i] = NULL;
16925         }
16926
16927         storedGames++;
16928         forwardMostMove = firstMove; // truncate game so we can start variation
16929 }
16930
16931 void
16932 PushTail(int firstMove, int lastMove)
16933 {
16934         if(appData.icsActive) { // only in local mode
16935                 forwardMostMove = currentMove; // mimic old ICS behavior
16936                 return;
16937         }
16938         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16939
16940         PushInner(firstMove, lastMove);
16941         if(storedGames == 1) GreyRevert(FALSE);
16942         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16943 }
16944
16945 void
16946 PopInner(Boolean annotate)
16947 {
16948         int i, j, nrMoves;
16949         char buf[8000], moveBuf[20];
16950
16951         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16952         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16953         nrMoves = savedLast[storedGames] - currentMove;
16954         if(annotate) {
16955                 int cnt = 10;
16956                 if(!WhiteOnMove(currentMove))
16957                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16958                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16959                 for(i=currentMove; i<forwardMostMove; i++) {
16960                         if(WhiteOnMove(i))
16961                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16962                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16963                         strcat(buf, moveBuf);
16964                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16965                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16966                 }
16967                 strcat(buf, ")");
16968         }
16969         for(i=1; i<=nrMoves; i++) { // copy last variation back
16970             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16971             for(j=0; j<MOVE_LEN; j++)
16972                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16973             for(j=0; j<2*MOVE_LEN; j++)
16974                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16975             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16976             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16977             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16978             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16979             commentList[currentMove+i] = commentList[framePtr+i];
16980             commentList[framePtr+i] = NULL;
16981         }
16982         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16983         framePtr = savedFramePtr[storedGames];
16984         gameInfo.result = savedResult[storedGames];
16985         if(gameInfo.resultDetails != NULL) {
16986             free(gameInfo.resultDetails);
16987       }
16988         gameInfo.resultDetails = savedDetails[storedGames];
16989         forwardMostMove = currentMove + nrMoves;
16990 }
16991
16992 Boolean
16993 PopTail(Boolean annotate)
16994 {
16995         if(appData.icsActive) return FALSE; // only in local mode
16996         if(!storedGames) return FALSE; // sanity
16997         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16998
16999         PopInner(annotate);
17000         if(currentMove < forwardMostMove) ForwardEvent(); else
17001         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17002
17003         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17004         return TRUE;
17005 }
17006
17007 void
17008 CleanupTail()
17009 {       // remove all shelved variations
17010         int i;
17011         for(i=0; i<storedGames; i++) {
17012             if(savedDetails[i])
17013                 free(savedDetails[i]);
17014             savedDetails[i] = NULL;
17015         }
17016         for(i=framePtr; i<MAX_MOVES; i++) {
17017                 if(commentList[i]) free(commentList[i]);
17018                 commentList[i] = NULL;
17019         }
17020         framePtr = MAX_MOVES-1;
17021         storedGames = 0;
17022 }
17023
17024 void
17025 LoadVariation(int index, char *text)
17026 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17027         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17028         int level = 0, move;
17029
17030         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17031         // first find outermost bracketing variation
17032         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17033             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17034                 if(*p == '{') wait = '}'; else
17035                 if(*p == '[') wait = ']'; else
17036                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17037                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17038             }
17039             if(*p == wait) wait = NULLCHAR; // closing ]} found
17040             p++;
17041         }
17042         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17043         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17044         end[1] = NULLCHAR; // clip off comment beyond variation
17045         ToNrEvent(currentMove-1);
17046         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17047         // kludge: use ParsePV() to append variation to game
17048         move = currentMove;
17049         ParsePV(start, TRUE, TRUE);
17050         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17051         ClearPremoveHighlights();
17052         CommentPopDown();
17053         ToNrEvent(currentMove+1);
17054 }
17055