8dc8f07e8b1f8a6eeb8b559ad0766586d5f8b4fb
[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             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10477             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10478                 first.twoMachinesColor = "black\n";
10479                 second.twoMachinesColor = "white\n";
10480             } else {
10481                 first.twoMachinesColor = "white\n";
10482                 second.twoMachinesColor = "black\n";
10483             }
10484         }
10485     }
10486     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10487         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10488       ExitAnalyzeMode();
10489     gameMode = nextGameMode;
10490     ModeHighlight();
10491     endingGame = 0;  /* [HGM] crash */
10492     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10493         if(matchMode == TRUE) { // match through command line: exit with or without popup
10494             if(ranking) {
10495                 ToNrEvent(forwardMostMove);
10496                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10497                 else ExitEvent(0);
10498             } else DisplayFatalError(buf, 0, 0);
10499         } else { // match through menu; just stop, with or without popup
10500             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10501             ModeHighlight();
10502             if(ranking){
10503                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10504             } else DisplayNote(buf);
10505       }
10506       if(ranking) free(ranking);
10507     }
10508 }
10509
10510 /* Assumes program was just initialized (initString sent).
10511    Leaves program in force mode. */
10512 void
10513 FeedMovesToProgram(cps, upto)
10514      ChessProgramState *cps;
10515      int upto;
10516 {
10517     int i;
10518
10519     if (appData.debugMode)
10520       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10521               startedFromSetupPosition ? "position and " : "",
10522               backwardMostMove, upto, cps->which);
10523     if(currentlyInitializedVariant != gameInfo.variant) {
10524       char buf[MSG_SIZ];
10525         // [HGM] variantswitch: make engine aware of new variant
10526         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10527                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10528         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10529         SendToProgram(buf, cps);
10530         currentlyInitializedVariant = gameInfo.variant;
10531     }
10532     SendToProgram("force\n", cps);
10533     if (startedFromSetupPosition) {
10534         SendBoard(cps, backwardMostMove);
10535     if (appData.debugMode) {
10536         fprintf(debugFP, "feedMoves\n");
10537     }
10538     }
10539     for (i = backwardMostMove; i < upto; i++) {
10540         SendMoveToProgram(i, cps);
10541     }
10542 }
10543
10544
10545 int
10546 ResurrectChessProgram()
10547 {
10548      /* The chess program may have exited.
10549         If so, restart it and feed it all the moves made so far. */
10550     static int doInit = 0;
10551
10552     if (appData.noChessProgram) return 1;
10553
10554     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10555         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10556         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10557         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10558     } else {
10559         if (first.pr != NoProc) return 1;
10560         StartChessProgram(&first);
10561     }
10562     InitChessProgram(&first, FALSE);
10563     FeedMovesToProgram(&first, currentMove);
10564
10565     if (!first.sendTime) {
10566         /* can't tell gnuchess what its clock should read,
10567            so we bow to its notion. */
10568         ResetClocks();
10569         timeRemaining[0][currentMove] = whiteTimeRemaining;
10570         timeRemaining[1][currentMove] = blackTimeRemaining;
10571     }
10572
10573     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10574                 appData.icsEngineAnalyze) && first.analysisSupport) {
10575       SendToProgram("analyze\n", &first);
10576       first.analyzing = TRUE;
10577     }
10578     return 1;
10579 }
10580
10581 /*
10582  * Button procedures
10583  */
10584 void
10585 Reset(redraw, init)
10586      int redraw, init;
10587 {
10588     int i;
10589
10590     if (appData.debugMode) {
10591         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10592                 redraw, init, gameMode);
10593     }
10594     CleanupTail(); // [HGM] vari: delete any stored variations
10595     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10596     pausing = pauseExamInvalid = FALSE;
10597     startedFromSetupPosition = blackPlaysFirst = FALSE;
10598     firstMove = TRUE;
10599     whiteFlag = blackFlag = FALSE;
10600     userOfferedDraw = FALSE;
10601     hintRequested = bookRequested = FALSE;
10602     first.maybeThinking = FALSE;
10603     second.maybeThinking = FALSE;
10604     first.bookSuspend = FALSE; // [HGM] book
10605     second.bookSuspend = FALSE;
10606     thinkOutput[0] = NULLCHAR;
10607     lastHint[0] = NULLCHAR;
10608     ClearGameInfo(&gameInfo);
10609     gameInfo.variant = StringToVariant(appData.variant);
10610     ics_user_moved = ics_clock_paused = FALSE;
10611     ics_getting_history = H_FALSE;
10612     ics_gamenum = -1;
10613     white_holding[0] = black_holding[0] = NULLCHAR;
10614     ClearProgramStats();
10615     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10616
10617     ResetFrontEnd();
10618     ClearHighlights();
10619     flipView = appData.flipView;
10620     ClearPremoveHighlights();
10621     gotPremove = FALSE;
10622     alarmSounded = FALSE;
10623
10624     GameEnds(EndOfFile, NULL, GE_PLAYER);
10625     if(appData.serverMovesName != NULL) {
10626         /* [HGM] prepare to make moves file for broadcasting */
10627         clock_t t = clock();
10628         if(serverMoves != NULL) fclose(serverMoves);
10629         serverMoves = fopen(appData.serverMovesName, "r");
10630         if(serverMoves != NULL) {
10631             fclose(serverMoves);
10632             /* delay 15 sec before overwriting, so all clients can see end */
10633             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10634         }
10635         serverMoves = fopen(appData.serverMovesName, "w");
10636     }
10637
10638     ExitAnalyzeMode();
10639     gameMode = BeginningOfGame;
10640     ModeHighlight();
10641     if(appData.icsActive) gameInfo.variant = VariantNormal;
10642     currentMove = forwardMostMove = backwardMostMove = 0;
10643     InitPosition(redraw);
10644     for (i = 0; i < MAX_MOVES; i++) {
10645         if (commentList[i] != NULL) {
10646             free(commentList[i]);
10647             commentList[i] = NULL;
10648         }
10649     }
10650     ResetClocks();
10651     timeRemaining[0][0] = whiteTimeRemaining;
10652     timeRemaining[1][0] = blackTimeRemaining;
10653
10654     if (first.pr == NoProc) {
10655         StartChessProgram(&first);
10656     }
10657     if (init) {
10658             InitChessProgram(&first, startedFromSetupPosition);
10659     }
10660     DisplayTitle("");
10661     DisplayMessage("", "");
10662     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10663     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10664 }
10665
10666 void
10667 AutoPlayGameLoop()
10668 {
10669     for (;;) {
10670         if (!AutoPlayOneMove())
10671           return;
10672         if (matchMode || appData.timeDelay == 0)
10673           continue;
10674         if (appData.timeDelay < 0)
10675           return;
10676         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10677         break;
10678     }
10679 }
10680
10681
10682 int
10683 AutoPlayOneMove()
10684 {
10685     int fromX, fromY, toX, toY;
10686
10687     if (appData.debugMode) {
10688       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10689     }
10690
10691     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10692       return FALSE;
10693
10694     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10695       pvInfoList[currentMove].depth = programStats.depth;
10696       pvInfoList[currentMove].score = programStats.score;
10697       pvInfoList[currentMove].time  = 0;
10698       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10699     }
10700
10701     if (currentMove >= forwardMostMove) {
10702       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10703 //      gameMode = EndOfGame;
10704 //      ModeHighlight();
10705
10706       /* [AS] Clear current move marker at the end of a game */
10707       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10708
10709       return FALSE;
10710     }
10711
10712     toX = moveList[currentMove][2] - AAA;
10713     toY = moveList[currentMove][3] - ONE;
10714
10715     if (moveList[currentMove][1] == '@') {
10716         if (appData.highlightLastMove) {
10717             SetHighlights(-1, -1, toX, toY);
10718         }
10719     } else {
10720         fromX = moveList[currentMove][0] - AAA;
10721         fromY = moveList[currentMove][1] - ONE;
10722
10723         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10724
10725         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10726
10727         if (appData.highlightLastMove) {
10728             SetHighlights(fromX, fromY, toX, toY);
10729         }
10730     }
10731     DisplayMove(currentMove);
10732     SendMoveToProgram(currentMove++, &first);
10733     DisplayBothClocks();
10734     DrawPosition(FALSE, boards[currentMove]);
10735     // [HGM] PV info: always display, routine tests if empty
10736     DisplayComment(currentMove - 1, commentList[currentMove]);
10737     return TRUE;
10738 }
10739
10740
10741 int
10742 LoadGameOneMove(readAhead)
10743      ChessMove readAhead;
10744 {
10745     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10746     char promoChar = NULLCHAR;
10747     ChessMove moveType;
10748     char move[MSG_SIZ];
10749     char *p, *q;
10750
10751     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10752         gameMode != AnalyzeMode && gameMode != Training) {
10753         gameFileFP = NULL;
10754         return FALSE;
10755     }
10756
10757     yyboardindex = forwardMostMove;
10758     if (readAhead != EndOfFile) {
10759       moveType = readAhead;
10760     } else {
10761       if (gameFileFP == NULL)
10762           return FALSE;
10763       moveType = (ChessMove) Myylex();
10764     }
10765
10766     done = FALSE;
10767     switch (moveType) {
10768       case Comment:
10769         if (appData.debugMode)
10770           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10771         p = yy_text;
10772
10773         /* append the comment but don't display it */
10774         AppendComment(currentMove, p, FALSE);
10775         return TRUE;
10776
10777       case WhiteCapturesEnPassant:
10778       case BlackCapturesEnPassant:
10779       case WhitePromotion:
10780       case BlackPromotion:
10781       case WhiteNonPromotion:
10782       case BlackNonPromotion:
10783       case NormalMove:
10784       case WhiteKingSideCastle:
10785       case WhiteQueenSideCastle:
10786       case BlackKingSideCastle:
10787       case BlackQueenSideCastle:
10788       case WhiteKingSideCastleWild:
10789       case WhiteQueenSideCastleWild:
10790       case BlackKingSideCastleWild:
10791       case BlackQueenSideCastleWild:
10792       /* PUSH Fabien */
10793       case WhiteHSideCastleFR:
10794       case WhiteASideCastleFR:
10795       case BlackHSideCastleFR:
10796       case BlackASideCastleFR:
10797       /* POP Fabien */
10798         if (appData.debugMode)
10799           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10800         fromX = currentMoveString[0] - AAA;
10801         fromY = currentMoveString[1] - ONE;
10802         toX = currentMoveString[2] - AAA;
10803         toY = currentMoveString[3] - ONE;
10804         promoChar = currentMoveString[4];
10805         break;
10806
10807       case WhiteDrop:
10808       case BlackDrop:
10809         if (appData.debugMode)
10810           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10811         fromX = moveType == WhiteDrop ?
10812           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10813         (int) CharToPiece(ToLower(currentMoveString[0]));
10814         fromY = DROP_RANK;
10815         toX = currentMoveString[2] - AAA;
10816         toY = currentMoveString[3] - ONE;
10817         break;
10818
10819       case WhiteWins:
10820       case BlackWins:
10821       case GameIsDrawn:
10822       case GameUnfinished:
10823         if (appData.debugMode)
10824           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10825         p = strchr(yy_text, '{');
10826         if (p == NULL) p = strchr(yy_text, '(');
10827         if (p == NULL) {
10828             p = yy_text;
10829             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10830         } else {
10831             q = strchr(p, *p == '{' ? '}' : ')');
10832             if (q != NULL) *q = NULLCHAR;
10833             p++;
10834         }
10835         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10836         GameEnds(moveType, p, GE_FILE);
10837         done = TRUE;
10838         if (cmailMsgLoaded) {
10839             ClearHighlights();
10840             flipView = WhiteOnMove(currentMove);
10841             if (moveType == GameUnfinished) flipView = !flipView;
10842             if (appData.debugMode)
10843               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10844         }
10845         break;
10846
10847       case EndOfFile:
10848         if (appData.debugMode)
10849           fprintf(debugFP, "Parser hit end of file\n");
10850         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10851           case MT_NONE:
10852           case MT_CHECK:
10853             break;
10854           case MT_CHECKMATE:
10855           case MT_STAINMATE:
10856             if (WhiteOnMove(currentMove)) {
10857                 GameEnds(BlackWins, "Black mates", GE_FILE);
10858             } else {
10859                 GameEnds(WhiteWins, "White mates", GE_FILE);
10860             }
10861             break;
10862           case MT_STALEMATE:
10863             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10864             break;
10865         }
10866         done = TRUE;
10867         break;
10868
10869       case MoveNumberOne:
10870         if (lastLoadGameStart == GNUChessGame) {
10871             /* GNUChessGames have numbers, but they aren't move numbers */
10872             if (appData.debugMode)
10873               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10874                       yy_text, (int) moveType);
10875             return LoadGameOneMove(EndOfFile); /* tail recursion */
10876         }
10877         /* else fall thru */
10878
10879       case XBoardGame:
10880       case GNUChessGame:
10881       case PGNTag:
10882         /* Reached start of next game in file */
10883         if (appData.debugMode)
10884           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10885         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10886           case MT_NONE:
10887           case MT_CHECK:
10888             break;
10889           case MT_CHECKMATE:
10890           case MT_STAINMATE:
10891             if (WhiteOnMove(currentMove)) {
10892                 GameEnds(BlackWins, "Black mates", GE_FILE);
10893             } else {
10894                 GameEnds(WhiteWins, "White mates", GE_FILE);
10895             }
10896             break;
10897           case MT_STALEMATE:
10898             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10899             break;
10900         }
10901         done = TRUE;
10902         break;
10903
10904       case PositionDiagram:     /* should not happen; ignore */
10905       case ElapsedTime:         /* ignore */
10906       case NAG:                 /* ignore */
10907         if (appData.debugMode)
10908           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10909                   yy_text, (int) moveType);
10910         return LoadGameOneMove(EndOfFile); /* tail recursion */
10911
10912       case IllegalMove:
10913         if (appData.testLegality) {
10914             if (appData.debugMode)
10915               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10916             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10917                     (forwardMostMove / 2) + 1,
10918                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10919             DisplayError(move, 0);
10920             done = TRUE;
10921         } else {
10922             if (appData.debugMode)
10923               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10924                       yy_text, currentMoveString);
10925             fromX = currentMoveString[0] - AAA;
10926             fromY = currentMoveString[1] - ONE;
10927             toX = currentMoveString[2] - AAA;
10928             toY = currentMoveString[3] - ONE;
10929             promoChar = currentMoveString[4];
10930         }
10931         break;
10932
10933       case AmbiguousMove:
10934         if (appData.debugMode)
10935           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10936         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10937                 (forwardMostMove / 2) + 1,
10938                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10939         DisplayError(move, 0);
10940         done = TRUE;
10941         break;
10942
10943       default:
10944       case ImpossibleMove:
10945         if (appData.debugMode)
10946           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10947         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10948                 (forwardMostMove / 2) + 1,
10949                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10950         DisplayError(move, 0);
10951         done = TRUE;
10952         break;
10953     }
10954
10955     if (done) {
10956         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10957             DrawPosition(FALSE, boards[currentMove]);
10958             DisplayBothClocks();
10959             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10960               DisplayComment(currentMove - 1, commentList[currentMove]);
10961         }
10962         (void) StopLoadGameTimer();
10963         gameFileFP = NULL;
10964         cmailOldMove = forwardMostMove;
10965         return FALSE;
10966     } else {
10967         /* currentMoveString is set as a side-effect of yylex */
10968
10969         thinkOutput[0] = NULLCHAR;
10970         MakeMove(fromX, fromY, toX, toY, promoChar);
10971         currentMove = forwardMostMove;
10972         return TRUE;
10973     }
10974 }
10975
10976 /* Load the nth game from the given file */
10977 int
10978 LoadGameFromFile(filename, n, title, useList)
10979      char *filename;
10980      int n;
10981      char *title;
10982      /*Boolean*/ int useList;
10983 {
10984     FILE *f;
10985     char buf[MSG_SIZ];
10986
10987     if (strcmp(filename, "-") == 0) {
10988         f = stdin;
10989         title = "stdin";
10990     } else {
10991         f = fopen(filename, "rb");
10992         if (f == NULL) {
10993           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10994             DisplayError(buf, errno);
10995             return FALSE;
10996         }
10997     }
10998     if (fseek(f, 0, 0) == -1) {
10999         /* f is not seekable; probably a pipe */
11000         useList = FALSE;
11001     }
11002     if (useList && n == 0) {
11003         int error = GameListBuild(f);
11004         if (error) {
11005             DisplayError(_("Cannot build game list"), error);
11006         } else if (!ListEmpty(&gameList) &&
11007                    ((ListGame *) gameList.tailPred)->number > 1) {
11008             GameListPopUp(f, title);
11009             return TRUE;
11010         }
11011         GameListDestroy();
11012         n = 1;
11013     }
11014     if (n == 0) n = 1;
11015     return LoadGame(f, n, title, FALSE);
11016 }
11017
11018
11019 void
11020 MakeRegisteredMove()
11021 {
11022     int fromX, fromY, toX, toY;
11023     char promoChar;
11024     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11025         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11026           case CMAIL_MOVE:
11027           case CMAIL_DRAW:
11028             if (appData.debugMode)
11029               fprintf(debugFP, "Restoring %s for game %d\n",
11030                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11031
11032             thinkOutput[0] = NULLCHAR;
11033             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11034             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11035             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11036             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11037             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11038             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11039             MakeMove(fromX, fromY, toX, toY, promoChar);
11040             ShowMove(fromX, fromY, toX, toY);
11041
11042             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11043               case MT_NONE:
11044               case MT_CHECK:
11045                 break;
11046
11047               case MT_CHECKMATE:
11048               case MT_STAINMATE:
11049                 if (WhiteOnMove(currentMove)) {
11050                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11051                 } else {
11052                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11053                 }
11054                 break;
11055
11056               case MT_STALEMATE:
11057                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11058                 break;
11059             }
11060
11061             break;
11062
11063           case CMAIL_RESIGN:
11064             if (WhiteOnMove(currentMove)) {
11065                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11066             } else {
11067                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11068             }
11069             break;
11070
11071           case CMAIL_ACCEPT:
11072             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11073             break;
11074
11075           default:
11076             break;
11077         }
11078     }
11079
11080     return;
11081 }
11082
11083 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11084 int
11085 CmailLoadGame(f, gameNumber, title, useList)
11086      FILE *f;
11087      int gameNumber;
11088      char *title;
11089      int useList;
11090 {
11091     int retVal;
11092
11093     if (gameNumber > nCmailGames) {
11094         DisplayError(_("No more games in this message"), 0);
11095         return FALSE;
11096     }
11097     if (f == lastLoadGameFP) {
11098         int offset = gameNumber - lastLoadGameNumber;
11099         if (offset == 0) {
11100             cmailMsg[0] = NULLCHAR;
11101             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11102                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11103                 nCmailMovesRegistered--;
11104             }
11105             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11106             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11107                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11108             }
11109         } else {
11110             if (! RegisterMove()) return FALSE;
11111         }
11112     }
11113
11114     retVal = LoadGame(f, gameNumber, title, useList);
11115
11116     /* Make move registered during previous look at this game, if any */
11117     MakeRegisteredMove();
11118
11119     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11120         commentList[currentMove]
11121           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11122         DisplayComment(currentMove - 1, commentList[currentMove]);
11123     }
11124
11125     return retVal;
11126 }
11127
11128 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11129 int
11130 ReloadGame(offset)
11131      int offset;
11132 {
11133     int gameNumber = lastLoadGameNumber + offset;
11134     if (lastLoadGameFP == NULL) {
11135         DisplayError(_("No game has been loaded yet"), 0);
11136         return FALSE;
11137     }
11138     if (gameNumber <= 0) {
11139         DisplayError(_("Can't back up any further"), 0);
11140         return FALSE;
11141     }
11142     if (cmailMsgLoaded) {
11143         return CmailLoadGame(lastLoadGameFP, gameNumber,
11144                              lastLoadGameTitle, lastLoadGameUseList);
11145     } else {
11146         return LoadGame(lastLoadGameFP, gameNumber,
11147                         lastLoadGameTitle, lastLoadGameUseList);
11148     }
11149 }
11150
11151 int keys[EmptySquare+1];
11152
11153 int
11154 PositionMatches(Board b1, Board b2)
11155 {
11156     int r, f, sum=0;
11157     switch(appData.searchMode) {
11158         case 1: return CompareWithRights(b1, b2);
11159         case 2:
11160             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11161                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11162             }
11163             return TRUE;
11164         case 3:
11165             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11166               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11167                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11168             }
11169             return sum==0;
11170         case 4:
11171             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11172                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11173             }
11174             return sum==0;
11175     }
11176     return TRUE;
11177 }
11178
11179 #define Q_PROMO  4
11180 #define Q_EP     3
11181 #define Q_BCASTL 2
11182 #define Q_WCASTL 1
11183
11184 int pieceList[256], quickBoard[256];
11185 ChessSquare pieceType[256] = { EmptySquare };
11186 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11187 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11188 int soughtTotal, turn;
11189 Boolean epOK, flipSearch;
11190
11191 typedef struct {
11192     unsigned char piece, to;
11193 } Move;
11194
11195 #define DSIZE (250000)
11196
11197 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11198 Move *moveDatabase = initialSpace;
11199 unsigned int movePtr, dataSize = DSIZE;
11200
11201 int MakePieceList(Board board, int *counts)
11202 {
11203     int r, f, n=Q_PROMO, total=0;
11204     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11205     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11206         int sq = f + (r<<4);
11207         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11208             quickBoard[sq] = ++n;
11209             pieceList[n] = sq;
11210             pieceType[n] = board[r][f];
11211             counts[board[r][f]]++;
11212             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11213             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11214             total++;
11215         }
11216     }
11217     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11218     return total;
11219 }
11220
11221 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11222 {
11223     int sq = fromX + (fromY<<4);
11224     int piece = quickBoard[sq];
11225     quickBoard[sq] = 0;
11226     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11227     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11228         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11229         moveDatabase[movePtr++].piece = Q_WCASTL;
11230         quickBoard[sq] = piece;
11231         piece = quickBoard[from]; quickBoard[from] = 0;
11232         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11233     } else
11234     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11235         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11236         moveDatabase[movePtr++].piece = Q_BCASTL;
11237         quickBoard[sq] = piece;
11238         piece = quickBoard[from]; quickBoard[from] = 0;
11239         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11240     } else
11241     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11242         quickBoard[(fromY<<4)+toX] = 0;
11243         moveDatabase[movePtr].piece = Q_EP;
11244         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11245         moveDatabase[movePtr].to = sq;
11246     } else
11247     if(promoPiece != pieceType[piece]) {
11248         moveDatabase[movePtr++].piece = Q_PROMO;
11249         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11250     }
11251     moveDatabase[movePtr].piece = piece;
11252     quickBoard[sq] = piece;
11253     movePtr++;
11254 }
11255
11256 int PackGame(Board board)
11257 {
11258     Move *newSpace = NULL;
11259     moveDatabase[movePtr].piece = 0; // terminate previous game
11260     if(movePtr > dataSize) {
11261         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11262         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11263         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11264         if(newSpace) {
11265             int i;
11266             Move *p = moveDatabase, *q = newSpace;
11267             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11268             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11269             moveDatabase = newSpace;
11270         } else { // calloc failed, we must be out of memory. Too bad...
11271             dataSize = 0; // prevent calloc events for all subsequent games
11272             return 0;     // and signal this one isn't cached
11273         }
11274     }
11275     movePtr++;
11276     MakePieceList(board, counts);
11277     return movePtr;
11278 }
11279
11280 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11281 {   // compare according to search mode
11282     int r, f;
11283     switch(appData.searchMode)
11284     {
11285       case 1: // exact position match
11286         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11287         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11288             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11289         }
11290         break;
11291       case 2: // can have extra material on empty squares
11292         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11293             if(board[r][f] == EmptySquare) continue;
11294             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11295         }
11296         break;
11297       case 3: // material with exact Pawn structure
11298         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11299             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11300             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11301         } // fall through to material comparison
11302       case 4: // exact material
11303         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11304         break;
11305       case 6: // material range with given imbalance
11306         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11307         // fall through to range comparison
11308       case 5: // material range
11309         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11310     }
11311     return TRUE;
11312 }
11313
11314 int QuickScan(Board board, Move *move)
11315 {   // reconstruct game,and compare all positions in it
11316     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11317     do {
11318         int piece = move->piece;
11319         int to = move->to, from = pieceList[piece];
11320         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11321           if(!piece) return -1;
11322           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11323             piece = (++move)->piece;
11324             from = pieceList[piece];
11325             counts[pieceType[piece]]--;
11326             pieceType[piece] = (ChessSquare) move->to;
11327             counts[move->to]++;
11328           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11329             counts[pieceType[quickBoard[to]]]--;
11330             quickBoard[to] = 0; total--;
11331             move++;
11332             continue;
11333           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11334             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11335             from  = pieceList[piece]; // so this must be King
11336             quickBoard[from] = 0;
11337             quickBoard[to] = piece;
11338             pieceList[piece] = to;
11339             move++;
11340             continue;
11341           }
11342         }
11343         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11344         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11345         quickBoard[from] = 0;
11346         quickBoard[to] = piece;
11347         pieceList[piece] = to;
11348         cnt++; turn ^= 3;
11349         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11350            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11351            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11352                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11353           ) {
11354             static int lastCounts[EmptySquare+1];
11355             int i;
11356             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11357             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11358         } else stretch = 0;
11359         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11360         move++;
11361     } while(1);
11362 }
11363
11364 void InitSearch()
11365 {
11366     int r, f;
11367     flipSearch = FALSE;
11368     CopyBoard(soughtBoard, boards[currentMove]);
11369     soughtTotal = MakePieceList(soughtBoard, maxSought);
11370     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11371     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11372     CopyBoard(reverseBoard, boards[currentMove]);
11373     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11374         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11375         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11376         reverseBoard[r][f] = piece;
11377     }
11378     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11379     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11380     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11381                  || (boards[currentMove][CASTLING][2] == NoRights || 
11382                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11383                  && (boards[currentMove][CASTLING][5] == NoRights || 
11384                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11385       ) {
11386         flipSearch = TRUE;
11387         CopyBoard(flipBoard, soughtBoard);
11388         CopyBoard(rotateBoard, reverseBoard);
11389         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11390             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11391             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11392         }
11393     }
11394     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11395     if(appData.searchMode >= 5) {
11396         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11397         MakePieceList(soughtBoard, minSought);
11398         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11399     }
11400     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11401         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11402 }
11403
11404 GameInfo dummyInfo;
11405
11406 int GameContainsPosition(FILE *f, ListGame *lg)
11407 {
11408     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11409     int fromX, fromY, toX, toY;
11410     char promoChar;
11411     static int initDone=FALSE;
11412
11413     // weed out games based on numerical tag comparison
11414     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11415     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11416     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11417     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11418     if(!initDone) {
11419         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11420         initDone = TRUE;
11421     }
11422     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11423     else CopyBoard(boards[scratch], initialPosition); // default start position
11424     if(lg->moves) {
11425         turn = btm + 1;
11426         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11427         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11428     }
11429     if(btm) plyNr++;
11430     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11431     fseek(f, lg->offset, 0);
11432     yynewfile(f);
11433     while(1) {
11434         yyboardindex = scratch;
11435         quickFlag = plyNr+1;
11436         next = Myylex();
11437         quickFlag = 0;
11438         switch(next) {
11439             case PGNTag:
11440                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11441             default:
11442                 continue;
11443
11444             case XBoardGame:
11445             case GNUChessGame:
11446                 if(plyNr) return -1; // after we have seen moves, this is for new game
11447               continue;
11448
11449             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11450             case ImpossibleMove:
11451             case WhiteWins: // game ends here with these four
11452             case BlackWins:
11453             case GameIsDrawn:
11454             case GameUnfinished:
11455                 return -1;
11456
11457             case IllegalMove:
11458                 if(appData.testLegality) return -1;
11459             case WhiteCapturesEnPassant:
11460             case BlackCapturesEnPassant:
11461             case WhitePromotion:
11462             case BlackPromotion:
11463             case WhiteNonPromotion:
11464             case BlackNonPromotion:
11465             case NormalMove:
11466             case WhiteKingSideCastle:
11467             case WhiteQueenSideCastle:
11468             case BlackKingSideCastle:
11469             case BlackQueenSideCastle:
11470             case WhiteKingSideCastleWild:
11471             case WhiteQueenSideCastleWild:
11472             case BlackKingSideCastleWild:
11473             case BlackQueenSideCastleWild:
11474             case WhiteHSideCastleFR:
11475             case WhiteASideCastleFR:
11476             case BlackHSideCastleFR:
11477             case BlackASideCastleFR:
11478                 fromX = currentMoveString[0] - AAA;
11479                 fromY = currentMoveString[1] - ONE;
11480                 toX = currentMoveString[2] - AAA;
11481                 toY = currentMoveString[3] - ONE;
11482                 promoChar = currentMoveString[4];
11483                 break;
11484             case WhiteDrop:
11485             case BlackDrop:
11486                 fromX = next == WhiteDrop ?
11487                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11488                   (int) CharToPiece(ToLower(currentMoveString[0]));
11489                 fromY = DROP_RANK;
11490                 toX = currentMoveString[2] - AAA;
11491                 toY = currentMoveString[3] - ONE;
11492                 promoChar = 0;
11493                 break;
11494         }
11495         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11496         plyNr++;
11497         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11498         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11499         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11500         if(appData.findMirror) {
11501             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11502             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11503         }
11504     }
11505 }
11506
11507 /* Load the nth game from open file f */
11508 int
11509 LoadGame(f, gameNumber, title, useList)
11510      FILE *f;
11511      int gameNumber;
11512      char *title;
11513      int useList;
11514 {
11515     ChessMove cm;
11516     char buf[MSG_SIZ];
11517     int gn = gameNumber;
11518     ListGame *lg = NULL;
11519     int numPGNTags = 0;
11520     int err, pos = -1;
11521     GameMode oldGameMode;
11522     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11523
11524     if (appData.debugMode)
11525         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11526
11527     if (gameMode == Training )
11528         SetTrainingModeOff();
11529
11530     oldGameMode = gameMode;
11531     if (gameMode != BeginningOfGame) {
11532       Reset(FALSE, TRUE);
11533     }
11534
11535     gameFileFP = f;
11536     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11537         fclose(lastLoadGameFP);
11538     }
11539
11540     if (useList) {
11541         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11542
11543         if (lg) {
11544             fseek(f, lg->offset, 0);
11545             GameListHighlight(gameNumber);
11546             pos = lg->position;
11547             gn = 1;
11548         }
11549         else {
11550             DisplayError(_("Game number out of range"), 0);
11551             return FALSE;
11552         }
11553     } else {
11554         GameListDestroy();
11555         if (fseek(f, 0, 0) == -1) {
11556             if (f == lastLoadGameFP ?
11557                 gameNumber == lastLoadGameNumber + 1 :
11558                 gameNumber == 1) {
11559                 gn = 1;
11560             } else {
11561                 DisplayError(_("Can't seek on game file"), 0);
11562                 return FALSE;
11563             }
11564         }
11565     }
11566     lastLoadGameFP = f;
11567     lastLoadGameNumber = gameNumber;
11568     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11569     lastLoadGameUseList = useList;
11570
11571     yynewfile(f);
11572
11573     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11574       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11575                 lg->gameInfo.black);
11576             DisplayTitle(buf);
11577     } else if (*title != NULLCHAR) {
11578         if (gameNumber > 1) {
11579           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11580             DisplayTitle(buf);
11581         } else {
11582             DisplayTitle(title);
11583         }
11584     }
11585
11586     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11587         gameMode = PlayFromGameFile;
11588         ModeHighlight();
11589     }
11590
11591     currentMove = forwardMostMove = backwardMostMove = 0;
11592     CopyBoard(boards[0], initialPosition);
11593     StopClocks();
11594
11595     /*
11596      * Skip the first gn-1 games in the file.
11597      * Also skip over anything that precedes an identifiable
11598      * start of game marker, to avoid being confused by
11599      * garbage at the start of the file.  Currently
11600      * recognized start of game markers are the move number "1",
11601      * the pattern "gnuchess .* game", the pattern
11602      * "^[#;%] [^ ]* game file", and a PGN tag block.
11603      * A game that starts with one of the latter two patterns
11604      * will also have a move number 1, possibly
11605      * following a position diagram.
11606      * 5-4-02: Let's try being more lenient and allowing a game to
11607      * start with an unnumbered move.  Does that break anything?
11608      */
11609     cm = lastLoadGameStart = EndOfFile;
11610     while (gn > 0) {
11611         yyboardindex = forwardMostMove;
11612         cm = (ChessMove) Myylex();
11613         switch (cm) {
11614           case EndOfFile:
11615             if (cmailMsgLoaded) {
11616                 nCmailGames = CMAIL_MAX_GAMES - gn;
11617             } else {
11618                 Reset(TRUE, TRUE);
11619                 DisplayError(_("Game not found in file"), 0);
11620             }
11621             return FALSE;
11622
11623           case GNUChessGame:
11624           case XBoardGame:
11625             gn--;
11626             lastLoadGameStart = cm;
11627             break;
11628
11629           case MoveNumberOne:
11630             switch (lastLoadGameStart) {
11631               case GNUChessGame:
11632               case XBoardGame:
11633               case PGNTag:
11634                 break;
11635               case MoveNumberOne:
11636               case EndOfFile:
11637                 gn--;           /* count this game */
11638                 lastLoadGameStart = cm;
11639                 break;
11640               default:
11641                 /* impossible */
11642                 break;
11643             }
11644             break;
11645
11646           case PGNTag:
11647             switch (lastLoadGameStart) {
11648               case GNUChessGame:
11649               case PGNTag:
11650               case MoveNumberOne:
11651               case EndOfFile:
11652                 gn--;           /* count this game */
11653                 lastLoadGameStart = cm;
11654                 break;
11655               case XBoardGame:
11656                 lastLoadGameStart = cm; /* game counted already */
11657                 break;
11658               default:
11659                 /* impossible */
11660                 break;
11661             }
11662             if (gn > 0) {
11663                 do {
11664                     yyboardindex = forwardMostMove;
11665                     cm = (ChessMove) Myylex();
11666                 } while (cm == PGNTag || cm == Comment);
11667             }
11668             break;
11669
11670           case WhiteWins:
11671           case BlackWins:
11672           case GameIsDrawn:
11673             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11674                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11675                     != CMAIL_OLD_RESULT) {
11676                     nCmailResults ++ ;
11677                     cmailResult[  CMAIL_MAX_GAMES
11678                                 - gn - 1] = CMAIL_OLD_RESULT;
11679                 }
11680             }
11681             break;
11682
11683           case NormalMove:
11684             /* Only a NormalMove can be at the start of a game
11685              * without a position diagram. */
11686             if (lastLoadGameStart == EndOfFile ) {
11687               gn--;
11688               lastLoadGameStart = MoveNumberOne;
11689             }
11690             break;
11691
11692           default:
11693             break;
11694         }
11695     }
11696
11697     if (appData.debugMode)
11698       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11699
11700     if (cm == XBoardGame) {
11701         /* Skip any header junk before position diagram and/or move 1 */
11702         for (;;) {
11703             yyboardindex = forwardMostMove;
11704             cm = (ChessMove) Myylex();
11705
11706             if (cm == EndOfFile ||
11707                 cm == GNUChessGame || cm == XBoardGame) {
11708                 /* Empty game; pretend end-of-file and handle later */
11709                 cm = EndOfFile;
11710                 break;
11711             }
11712
11713             if (cm == MoveNumberOne || cm == PositionDiagram ||
11714                 cm == PGNTag || cm == Comment)
11715               break;
11716         }
11717     } else if (cm == GNUChessGame) {
11718         if (gameInfo.event != NULL) {
11719             free(gameInfo.event);
11720         }
11721         gameInfo.event = StrSave(yy_text);
11722     }
11723
11724     startedFromSetupPosition = FALSE;
11725     while (cm == PGNTag) {
11726         if (appData.debugMode)
11727           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11728         err = ParsePGNTag(yy_text, &gameInfo);
11729         if (!err) numPGNTags++;
11730
11731         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11732         if(gameInfo.variant != oldVariant) {
11733             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11734             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11735             InitPosition(TRUE);
11736             oldVariant = gameInfo.variant;
11737             if (appData.debugMode)
11738               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11739         }
11740
11741
11742         if (gameInfo.fen != NULL) {
11743           Board initial_position;
11744           startedFromSetupPosition = TRUE;
11745           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11746             Reset(TRUE, TRUE);
11747             DisplayError(_("Bad FEN position in file"), 0);
11748             return FALSE;
11749           }
11750           CopyBoard(boards[0], initial_position);
11751           if (blackPlaysFirst) {
11752             currentMove = forwardMostMove = backwardMostMove = 1;
11753             CopyBoard(boards[1], initial_position);
11754             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11755             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11756             timeRemaining[0][1] = whiteTimeRemaining;
11757             timeRemaining[1][1] = blackTimeRemaining;
11758             if (commentList[0] != NULL) {
11759               commentList[1] = commentList[0];
11760               commentList[0] = NULL;
11761             }
11762           } else {
11763             currentMove = forwardMostMove = backwardMostMove = 0;
11764           }
11765           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11766           {   int i;
11767               initialRulePlies = FENrulePlies;
11768               for( i=0; i< nrCastlingRights; i++ )
11769                   initialRights[i] = initial_position[CASTLING][i];
11770           }
11771           yyboardindex = forwardMostMove;
11772           free(gameInfo.fen);
11773           gameInfo.fen = NULL;
11774         }
11775
11776         yyboardindex = forwardMostMove;
11777         cm = (ChessMove) Myylex();
11778
11779         /* Handle comments interspersed among the tags */
11780         while (cm == Comment) {
11781             char *p;
11782             if (appData.debugMode)
11783               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11784             p = yy_text;
11785             AppendComment(currentMove, p, FALSE);
11786             yyboardindex = forwardMostMove;
11787             cm = (ChessMove) Myylex();
11788         }
11789     }
11790
11791     /* don't rely on existence of Event tag since if game was
11792      * pasted from clipboard the Event tag may not exist
11793      */
11794     if (numPGNTags > 0){
11795         char *tags;
11796         if (gameInfo.variant == VariantNormal) {
11797           VariantClass v = StringToVariant(gameInfo.event);
11798           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11799           if(v < VariantShogi) gameInfo.variant = v;
11800         }
11801         if (!matchMode) {
11802           if( appData.autoDisplayTags ) {
11803             tags = PGNTags(&gameInfo);
11804             TagsPopUp(tags, CmailMsg());
11805             free(tags);
11806           }
11807         }
11808     } else {
11809         /* Make something up, but don't display it now */
11810         SetGameInfo();
11811         TagsPopDown();
11812     }
11813
11814     if (cm == PositionDiagram) {
11815         int i, j;
11816         char *p;
11817         Board initial_position;
11818
11819         if (appData.debugMode)
11820           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11821
11822         if (!startedFromSetupPosition) {
11823             p = yy_text;
11824             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11825               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11826                 switch (*p) {
11827                   case '{':
11828                   case '[':
11829                   case '-':
11830                   case ' ':
11831                   case '\t':
11832                   case '\n':
11833                   case '\r':
11834                     break;
11835                   default:
11836                     initial_position[i][j++] = CharToPiece(*p);
11837                     break;
11838                 }
11839             while (*p == ' ' || *p == '\t' ||
11840                    *p == '\n' || *p == '\r') p++;
11841
11842             if (strncmp(p, "black", strlen("black"))==0)
11843               blackPlaysFirst = TRUE;
11844             else
11845               blackPlaysFirst = FALSE;
11846             startedFromSetupPosition = TRUE;
11847
11848             CopyBoard(boards[0], initial_position);
11849             if (blackPlaysFirst) {
11850                 currentMove = forwardMostMove = backwardMostMove = 1;
11851                 CopyBoard(boards[1], initial_position);
11852                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11853                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11854                 timeRemaining[0][1] = whiteTimeRemaining;
11855                 timeRemaining[1][1] = blackTimeRemaining;
11856                 if (commentList[0] != NULL) {
11857                     commentList[1] = commentList[0];
11858                     commentList[0] = NULL;
11859                 }
11860             } else {
11861                 currentMove = forwardMostMove = backwardMostMove = 0;
11862             }
11863         }
11864         yyboardindex = forwardMostMove;
11865         cm = (ChessMove) Myylex();
11866     }
11867
11868     if (first.pr == NoProc) {
11869         StartChessProgram(&first);
11870     }
11871     InitChessProgram(&first, FALSE);
11872     SendToProgram("force\n", &first);
11873     if (startedFromSetupPosition) {
11874         SendBoard(&first, forwardMostMove);
11875     if (appData.debugMode) {
11876         fprintf(debugFP, "Load Game\n");
11877     }
11878         DisplayBothClocks();
11879     }
11880
11881     /* [HGM] server: flag to write setup moves in broadcast file as one */
11882     loadFlag = appData.suppressLoadMoves;
11883
11884     while (cm == Comment) {
11885         char *p;
11886         if (appData.debugMode)
11887           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11888         p = yy_text;
11889         AppendComment(currentMove, p, FALSE);
11890         yyboardindex = forwardMostMove;
11891         cm = (ChessMove) Myylex();
11892     }
11893
11894     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11895         cm == WhiteWins || cm == BlackWins ||
11896         cm == GameIsDrawn || cm == GameUnfinished) {
11897         DisplayMessage("", _("No moves in game"));
11898         if (cmailMsgLoaded) {
11899             if (appData.debugMode)
11900               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11901             ClearHighlights();
11902             flipView = FALSE;
11903         }
11904         DrawPosition(FALSE, boards[currentMove]);
11905         DisplayBothClocks();
11906         gameMode = EditGame;
11907         ModeHighlight();
11908         gameFileFP = NULL;
11909         cmailOldMove = 0;
11910         return TRUE;
11911     }
11912
11913     // [HGM] PV info: routine tests if comment empty
11914     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11915         DisplayComment(currentMove - 1, commentList[currentMove]);
11916     }
11917     if (!matchMode && appData.timeDelay != 0)
11918       DrawPosition(FALSE, boards[currentMove]);
11919
11920     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11921       programStats.ok_to_send = 1;
11922     }
11923
11924     /* if the first token after the PGN tags is a move
11925      * and not move number 1, retrieve it from the parser
11926      */
11927     if (cm != MoveNumberOne)
11928         LoadGameOneMove(cm);
11929
11930     /* load the remaining moves from the file */
11931     while (LoadGameOneMove(EndOfFile)) {
11932       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11933       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11934     }
11935
11936     /* rewind to the start of the game */
11937     currentMove = backwardMostMove;
11938
11939     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11940
11941     if (oldGameMode == AnalyzeFile ||
11942         oldGameMode == AnalyzeMode) {
11943       AnalyzeFileEvent();
11944     }
11945
11946     if (!matchMode && pos >= 0) {
11947         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11948     } else
11949     if (matchMode || appData.timeDelay == 0) {
11950       ToEndEvent();
11951     } else if (appData.timeDelay > 0) {
11952       AutoPlayGameLoop();
11953     }
11954
11955     if (appData.debugMode)
11956         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11957
11958     loadFlag = 0; /* [HGM] true game starts */
11959     return TRUE;
11960 }
11961
11962 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11963 int
11964 ReloadPosition(offset)
11965      int offset;
11966 {
11967     int positionNumber = lastLoadPositionNumber + offset;
11968     if (lastLoadPositionFP == NULL) {
11969         DisplayError(_("No position has been loaded yet"), 0);
11970         return FALSE;
11971     }
11972     if (positionNumber <= 0) {
11973         DisplayError(_("Can't back up any further"), 0);
11974         return FALSE;
11975     }
11976     return LoadPosition(lastLoadPositionFP, positionNumber,
11977                         lastLoadPositionTitle);
11978 }
11979
11980 /* Load the nth position from the given file */
11981 int
11982 LoadPositionFromFile(filename, n, title)
11983      char *filename;
11984      int n;
11985      char *title;
11986 {
11987     FILE *f;
11988     char buf[MSG_SIZ];
11989
11990     if (strcmp(filename, "-") == 0) {
11991         return LoadPosition(stdin, n, "stdin");
11992     } else {
11993         f = fopen(filename, "rb");
11994         if (f == NULL) {
11995             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11996             DisplayError(buf, errno);
11997             return FALSE;
11998         } else {
11999             return LoadPosition(f, n, title);
12000         }
12001     }
12002 }
12003
12004 /* Load the nth position from the given open file, and close it */
12005 int
12006 LoadPosition(f, positionNumber, title)
12007      FILE *f;
12008      int positionNumber;
12009      char *title;
12010 {
12011     char *p, line[MSG_SIZ];
12012     Board initial_position;
12013     int i, j, fenMode, pn;
12014
12015     if (gameMode == Training )
12016         SetTrainingModeOff();
12017
12018     if (gameMode != BeginningOfGame) {
12019         Reset(FALSE, TRUE);
12020     }
12021     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12022         fclose(lastLoadPositionFP);
12023     }
12024     if (positionNumber == 0) positionNumber = 1;
12025     lastLoadPositionFP = f;
12026     lastLoadPositionNumber = positionNumber;
12027     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12028     if (first.pr == NoProc && !appData.noChessProgram) {
12029       StartChessProgram(&first);
12030       InitChessProgram(&first, FALSE);
12031     }
12032     pn = positionNumber;
12033     if (positionNumber < 0) {
12034         /* Negative position number means to seek to that byte offset */
12035         if (fseek(f, -positionNumber, 0) == -1) {
12036             DisplayError(_("Can't seek on position file"), 0);
12037             return FALSE;
12038         };
12039         pn = 1;
12040     } else {
12041         if (fseek(f, 0, 0) == -1) {
12042             if (f == lastLoadPositionFP ?
12043                 positionNumber == lastLoadPositionNumber + 1 :
12044                 positionNumber == 1) {
12045                 pn = 1;
12046             } else {
12047                 DisplayError(_("Can't seek on position file"), 0);
12048                 return FALSE;
12049             }
12050         }
12051     }
12052     /* See if this file is FEN or old-style xboard */
12053     if (fgets(line, MSG_SIZ, f) == NULL) {
12054         DisplayError(_("Position not found in file"), 0);
12055         return FALSE;
12056     }
12057     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12058     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12059
12060     if (pn >= 2) {
12061         if (fenMode || line[0] == '#') pn--;
12062         while (pn > 0) {
12063             /* skip positions before number pn */
12064             if (fgets(line, MSG_SIZ, f) == NULL) {
12065                 Reset(TRUE, TRUE);
12066                 DisplayError(_("Position not found in file"), 0);
12067                 return FALSE;
12068             }
12069             if (fenMode || line[0] == '#') pn--;
12070         }
12071     }
12072
12073     if (fenMode) {
12074         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12075             DisplayError(_("Bad FEN position in file"), 0);
12076             return FALSE;
12077         }
12078     } else {
12079         (void) fgets(line, MSG_SIZ, f);
12080         (void) fgets(line, MSG_SIZ, f);
12081
12082         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12083             (void) fgets(line, MSG_SIZ, f);
12084             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12085                 if (*p == ' ')
12086                   continue;
12087                 initial_position[i][j++] = CharToPiece(*p);
12088             }
12089         }
12090
12091         blackPlaysFirst = FALSE;
12092         if (!feof(f)) {
12093             (void) fgets(line, MSG_SIZ, f);
12094             if (strncmp(line, "black", strlen("black"))==0)
12095               blackPlaysFirst = TRUE;
12096         }
12097     }
12098     startedFromSetupPosition = TRUE;
12099
12100     CopyBoard(boards[0], initial_position);
12101     if (blackPlaysFirst) {
12102         currentMove = forwardMostMove = backwardMostMove = 1;
12103         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12104         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12105         CopyBoard(boards[1], initial_position);
12106         DisplayMessage("", _("Black to play"));
12107     } else {
12108         currentMove = forwardMostMove = backwardMostMove = 0;
12109         DisplayMessage("", _("White to play"));
12110     }
12111     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12112     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12113         SendToProgram("force\n", &first);
12114         SendBoard(&first, forwardMostMove);
12115     }
12116     if (appData.debugMode) {
12117 int i, j;
12118   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12119   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12120         fprintf(debugFP, "Load Position\n");
12121     }
12122
12123     if (positionNumber > 1) {
12124       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12125         DisplayTitle(line);
12126     } else {
12127         DisplayTitle(title);
12128     }
12129     gameMode = EditGame;
12130     ModeHighlight();
12131     ResetClocks();
12132     timeRemaining[0][1] = whiteTimeRemaining;
12133     timeRemaining[1][1] = blackTimeRemaining;
12134     DrawPosition(FALSE, boards[currentMove]);
12135
12136     return TRUE;
12137 }
12138
12139
12140 void
12141 CopyPlayerNameIntoFileName(dest, src)
12142      char **dest, *src;
12143 {
12144     while (*src != NULLCHAR && *src != ',') {
12145         if (*src == ' ') {
12146             *(*dest)++ = '_';
12147             src++;
12148         } else {
12149             *(*dest)++ = *src++;
12150         }
12151     }
12152 }
12153
12154 char *DefaultFileName(ext)
12155      char *ext;
12156 {
12157     static char def[MSG_SIZ];
12158     char *p;
12159
12160     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12161         p = def;
12162         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12163         *p++ = '-';
12164         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12165         *p++ = '.';
12166         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12167     } else {
12168         def[0] = NULLCHAR;
12169     }
12170     return def;
12171 }
12172
12173 /* Save the current game to the given file */
12174 int
12175 SaveGameToFile(filename, append)
12176      char *filename;
12177      int append;
12178 {
12179     FILE *f;
12180     char buf[MSG_SIZ];
12181     int result, i, t,tot=0;
12182
12183     if (strcmp(filename, "-") == 0) {
12184         return SaveGame(stdout, 0, NULL);
12185     } else {
12186         for(i=0; i<10; i++) { // upto 10 tries
12187              f = fopen(filename, append ? "a" : "w");
12188              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12189              if(f || errno != 13) break;
12190              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12191              tot += t;
12192         }
12193         if (f == NULL) {
12194             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12195             DisplayError(buf, errno);
12196             return FALSE;
12197         } else {
12198             safeStrCpy(buf, lastMsg, MSG_SIZ);
12199             DisplayMessage(_("Waiting for access to save file"), "");
12200             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12201             DisplayMessage(_("Saving game"), "");
12202             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12203             result = SaveGame(f, 0, NULL);
12204             DisplayMessage(buf, "");
12205             return result;
12206         }
12207     }
12208 }
12209
12210 char *
12211 SavePart(str)
12212      char *str;
12213 {
12214     static char buf[MSG_SIZ];
12215     char *p;
12216
12217     p = strchr(str, ' ');
12218     if (p == NULL) return str;
12219     strncpy(buf, str, p - str);
12220     buf[p - str] = NULLCHAR;
12221     return buf;
12222 }
12223
12224 #define PGN_MAX_LINE 75
12225
12226 #define PGN_SIDE_WHITE  0
12227 #define PGN_SIDE_BLACK  1
12228
12229 /* [AS] */
12230 static int FindFirstMoveOutOfBook( int side )
12231 {
12232     int result = -1;
12233
12234     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12235         int index = backwardMostMove;
12236         int has_book_hit = 0;
12237
12238         if( (index % 2) != side ) {
12239             index++;
12240         }
12241
12242         while( index < forwardMostMove ) {
12243             /* Check to see if engine is in book */
12244             int depth = pvInfoList[index].depth;
12245             int score = pvInfoList[index].score;
12246             int in_book = 0;
12247
12248             if( depth <= 2 ) {
12249                 in_book = 1;
12250             }
12251             else if( score == 0 && depth == 63 ) {
12252                 in_book = 1; /* Zappa */
12253             }
12254             else if( score == 2 && depth == 99 ) {
12255                 in_book = 1; /* Abrok */
12256             }
12257
12258             has_book_hit += in_book;
12259
12260             if( ! in_book ) {
12261                 result = index;
12262
12263                 break;
12264             }
12265
12266             index += 2;
12267         }
12268     }
12269
12270     return result;
12271 }
12272
12273 /* [AS] */
12274 void GetOutOfBookInfo( char * buf )
12275 {
12276     int oob[2];
12277     int i;
12278     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12279
12280     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12281     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12282
12283     *buf = '\0';
12284
12285     if( oob[0] >= 0 || oob[1] >= 0 ) {
12286         for( i=0; i<2; i++ ) {
12287             int idx = oob[i];
12288
12289             if( idx >= 0 ) {
12290                 if( i > 0 && oob[0] >= 0 ) {
12291                     strcat( buf, "   " );
12292                 }
12293
12294                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12295                 sprintf( buf+strlen(buf), "%s%.2f",
12296                     pvInfoList[idx].score >= 0 ? "+" : "",
12297                     pvInfoList[idx].score / 100.0 );
12298             }
12299         }
12300     }
12301 }
12302
12303 /* Save game in PGN style and close the file */
12304 int
12305 SaveGamePGN(f)
12306      FILE *f;
12307 {
12308     int i, offset, linelen, newblock;
12309     time_t tm;
12310 //    char *movetext;
12311     char numtext[32];
12312     int movelen, numlen, blank;
12313     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12314
12315     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12316
12317     tm = time((time_t *) NULL);
12318
12319     PrintPGNTags(f, &gameInfo);
12320
12321     if (backwardMostMove > 0 || startedFromSetupPosition) {
12322         char *fen = PositionToFEN(backwardMostMove, NULL);
12323         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12324         fprintf(f, "\n{--------------\n");
12325         PrintPosition(f, backwardMostMove);
12326         fprintf(f, "--------------}\n");
12327         free(fen);
12328     }
12329     else {
12330         /* [AS] Out of book annotation */
12331         if( appData.saveOutOfBookInfo ) {
12332             char buf[64];
12333
12334             GetOutOfBookInfo( buf );
12335
12336             if( buf[0] != '\0' ) {
12337                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12338             }
12339         }
12340
12341         fprintf(f, "\n");
12342     }
12343
12344     i = backwardMostMove;
12345     linelen = 0;
12346     newblock = TRUE;
12347
12348     while (i < forwardMostMove) {
12349         /* Print comments preceding this move */
12350         if (commentList[i] != NULL) {
12351             if (linelen > 0) fprintf(f, "\n");
12352             fprintf(f, "%s", commentList[i]);
12353             linelen = 0;
12354             newblock = TRUE;
12355         }
12356
12357         /* Format move number */
12358         if ((i % 2) == 0)
12359           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12360         else
12361           if (newblock)
12362             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12363           else
12364             numtext[0] = NULLCHAR;
12365
12366         numlen = strlen(numtext);
12367         newblock = FALSE;
12368
12369         /* Print move number */
12370         blank = linelen > 0 && numlen > 0;
12371         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12372             fprintf(f, "\n");
12373             linelen = 0;
12374             blank = 0;
12375         }
12376         if (blank) {
12377             fprintf(f, " ");
12378             linelen++;
12379         }
12380         fprintf(f, "%s", numtext);
12381         linelen += numlen;
12382
12383         /* Get move */
12384         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12385         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12386
12387         /* Print move */
12388         blank = linelen > 0 && movelen > 0;
12389         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12390             fprintf(f, "\n");
12391             linelen = 0;
12392             blank = 0;
12393         }
12394         if (blank) {
12395             fprintf(f, " ");
12396             linelen++;
12397         }
12398         fprintf(f, "%s", move_buffer);
12399         linelen += movelen;
12400
12401         /* [AS] Add PV info if present */
12402         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12403             /* [HGM] add time */
12404             char buf[MSG_SIZ]; int seconds;
12405
12406             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12407
12408             if( seconds <= 0)
12409               buf[0] = 0;
12410             else
12411               if( seconds < 30 )
12412                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12413               else
12414                 {
12415                   seconds = (seconds + 4)/10; // round to full seconds
12416                   if( seconds < 60 )
12417                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12418                   else
12419                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12420                 }
12421
12422             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12423                       pvInfoList[i].score >= 0 ? "+" : "",
12424                       pvInfoList[i].score / 100.0,
12425                       pvInfoList[i].depth,
12426                       buf );
12427
12428             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12429
12430             /* Print score/depth */
12431             blank = linelen > 0 && movelen > 0;
12432             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12433                 fprintf(f, "\n");
12434                 linelen = 0;
12435                 blank = 0;
12436             }
12437             if (blank) {
12438                 fprintf(f, " ");
12439                 linelen++;
12440             }
12441             fprintf(f, "%s", move_buffer);
12442             linelen += movelen;
12443         }
12444
12445         i++;
12446     }
12447
12448     /* Start a new line */
12449     if (linelen > 0) fprintf(f, "\n");
12450
12451     /* Print comments after last move */
12452     if (commentList[i] != NULL) {
12453         fprintf(f, "%s\n", commentList[i]);
12454     }
12455
12456     /* Print result */
12457     if (gameInfo.resultDetails != NULL &&
12458         gameInfo.resultDetails[0] != NULLCHAR) {
12459         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12460                 PGNResult(gameInfo.result));
12461     } else {
12462         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12463     }
12464
12465     fclose(f);
12466     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12467     return TRUE;
12468 }
12469
12470 /* Save game in old style and close the file */
12471 int
12472 SaveGameOldStyle(f)
12473      FILE *f;
12474 {
12475     int i, offset;
12476     time_t tm;
12477
12478     tm = time((time_t *) NULL);
12479
12480     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12481     PrintOpponents(f);
12482
12483     if (backwardMostMove > 0 || startedFromSetupPosition) {
12484         fprintf(f, "\n[--------------\n");
12485         PrintPosition(f, backwardMostMove);
12486         fprintf(f, "--------------]\n");
12487     } else {
12488         fprintf(f, "\n");
12489     }
12490
12491     i = backwardMostMove;
12492     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12493
12494     while (i < forwardMostMove) {
12495         if (commentList[i] != NULL) {
12496             fprintf(f, "[%s]\n", commentList[i]);
12497         }
12498
12499         if ((i % 2) == 1) {
12500             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12501             i++;
12502         } else {
12503             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12504             i++;
12505             if (commentList[i] != NULL) {
12506                 fprintf(f, "\n");
12507                 continue;
12508             }
12509             if (i >= forwardMostMove) {
12510                 fprintf(f, "\n");
12511                 break;
12512             }
12513             fprintf(f, "%s\n", parseList[i]);
12514             i++;
12515         }
12516     }
12517
12518     if (commentList[i] != NULL) {
12519         fprintf(f, "[%s]\n", commentList[i]);
12520     }
12521
12522     /* This isn't really the old style, but it's close enough */
12523     if (gameInfo.resultDetails != NULL &&
12524         gameInfo.resultDetails[0] != NULLCHAR) {
12525         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12526                 gameInfo.resultDetails);
12527     } else {
12528         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12529     }
12530
12531     fclose(f);
12532     return TRUE;
12533 }
12534
12535 /* Save the current game to open file f and close the file */
12536 int
12537 SaveGame(f, dummy, dummy2)
12538      FILE *f;
12539      int dummy;
12540      char *dummy2;
12541 {
12542     if (gameMode == EditPosition) EditPositionDone(TRUE);
12543     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12544     if (appData.oldSaveStyle)
12545       return SaveGameOldStyle(f);
12546     else
12547       return SaveGamePGN(f);
12548 }
12549
12550 /* Save the current position to the given file */
12551 int
12552 SavePositionToFile(filename)
12553      char *filename;
12554 {
12555     FILE *f;
12556     char buf[MSG_SIZ];
12557
12558     if (strcmp(filename, "-") == 0) {
12559         return SavePosition(stdout, 0, NULL);
12560     } else {
12561         f = fopen(filename, "a");
12562         if (f == NULL) {
12563             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12564             DisplayError(buf, errno);
12565             return FALSE;
12566         } else {
12567             safeStrCpy(buf, lastMsg, MSG_SIZ);
12568             DisplayMessage(_("Waiting for access to save file"), "");
12569             flock(fileno(f), LOCK_EX); // [HGM] lock
12570             DisplayMessage(_("Saving position"), "");
12571             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12572             SavePosition(f, 0, NULL);
12573             DisplayMessage(buf, "");
12574             return TRUE;
12575         }
12576     }
12577 }
12578
12579 /* Save the current position to the given open file and close the file */
12580 int
12581 SavePosition(f, dummy, dummy2)
12582      FILE *f;
12583      int dummy;
12584      char *dummy2;
12585 {
12586     time_t tm;
12587     char *fen;
12588
12589     if (gameMode == EditPosition) EditPositionDone(TRUE);
12590     if (appData.oldSaveStyle) {
12591         tm = time((time_t *) NULL);
12592
12593         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12594         PrintOpponents(f);
12595         fprintf(f, "[--------------\n");
12596         PrintPosition(f, currentMove);
12597         fprintf(f, "--------------]\n");
12598     } else {
12599         fen = PositionToFEN(currentMove, NULL);
12600         fprintf(f, "%s\n", fen);
12601         free(fen);
12602     }
12603     fclose(f);
12604     return TRUE;
12605 }
12606
12607 void
12608 ReloadCmailMsgEvent(unregister)
12609      int unregister;
12610 {
12611 #if !WIN32
12612     static char *inFilename = NULL;
12613     static char *outFilename;
12614     int i;
12615     struct stat inbuf, outbuf;
12616     int status;
12617
12618     /* Any registered moves are unregistered if unregister is set, */
12619     /* i.e. invoked by the signal handler */
12620     if (unregister) {
12621         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12622             cmailMoveRegistered[i] = FALSE;
12623             if (cmailCommentList[i] != NULL) {
12624                 free(cmailCommentList[i]);
12625                 cmailCommentList[i] = NULL;
12626             }
12627         }
12628         nCmailMovesRegistered = 0;
12629     }
12630
12631     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12632         cmailResult[i] = CMAIL_NOT_RESULT;
12633     }
12634     nCmailResults = 0;
12635
12636     if (inFilename == NULL) {
12637         /* Because the filenames are static they only get malloced once  */
12638         /* and they never get freed                                      */
12639         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12640         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12641
12642         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12643         sprintf(outFilename, "%s.out", appData.cmailGameName);
12644     }
12645
12646     status = stat(outFilename, &outbuf);
12647     if (status < 0) {
12648         cmailMailedMove = FALSE;
12649     } else {
12650         status = stat(inFilename, &inbuf);
12651         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12652     }
12653
12654     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12655        counts the games, notes how each one terminated, etc.
12656
12657        It would be nice to remove this kludge and instead gather all
12658        the information while building the game list.  (And to keep it
12659        in the game list nodes instead of having a bunch of fixed-size
12660        parallel arrays.)  Note this will require getting each game's
12661        termination from the PGN tags, as the game list builder does
12662        not process the game moves.  --mann
12663        */
12664     cmailMsgLoaded = TRUE;
12665     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12666
12667     /* Load first game in the file or popup game menu */
12668     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12669
12670 #endif /* !WIN32 */
12671     return;
12672 }
12673
12674 int
12675 RegisterMove()
12676 {
12677     FILE *f;
12678     char string[MSG_SIZ];
12679
12680     if (   cmailMailedMove
12681         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12682         return TRUE;            /* Allow free viewing  */
12683     }
12684
12685     /* Unregister move to ensure that we don't leave RegisterMove        */
12686     /* with the move registered when the conditions for registering no   */
12687     /* longer hold                                                       */
12688     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12689         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12690         nCmailMovesRegistered --;
12691
12692         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12693           {
12694               free(cmailCommentList[lastLoadGameNumber - 1]);
12695               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12696           }
12697     }
12698
12699     if (cmailOldMove == -1) {
12700         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12701         return FALSE;
12702     }
12703
12704     if (currentMove > cmailOldMove + 1) {
12705         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12706         return FALSE;
12707     }
12708
12709     if (currentMove < cmailOldMove) {
12710         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12711         return FALSE;
12712     }
12713
12714     if (forwardMostMove > currentMove) {
12715         /* Silently truncate extra moves */
12716         TruncateGame();
12717     }
12718
12719     if (   (currentMove == cmailOldMove + 1)
12720         || (   (currentMove == cmailOldMove)
12721             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12722                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12723         if (gameInfo.result != GameUnfinished) {
12724             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12725         }
12726
12727         if (commentList[currentMove] != NULL) {
12728             cmailCommentList[lastLoadGameNumber - 1]
12729               = StrSave(commentList[currentMove]);
12730         }
12731         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12732
12733         if (appData.debugMode)
12734           fprintf(debugFP, "Saving %s for game %d\n",
12735                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12736
12737         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12738
12739         f = fopen(string, "w");
12740         if (appData.oldSaveStyle) {
12741             SaveGameOldStyle(f); /* also closes the file */
12742
12743             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12744             f = fopen(string, "w");
12745             SavePosition(f, 0, NULL); /* also closes the file */
12746         } else {
12747             fprintf(f, "{--------------\n");
12748             PrintPosition(f, currentMove);
12749             fprintf(f, "--------------}\n\n");
12750
12751             SaveGame(f, 0, NULL); /* also closes the file*/
12752         }
12753
12754         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12755         nCmailMovesRegistered ++;
12756     } else if (nCmailGames == 1) {
12757         DisplayError(_("You have not made a move yet"), 0);
12758         return FALSE;
12759     }
12760
12761     return TRUE;
12762 }
12763
12764 void
12765 MailMoveEvent()
12766 {
12767 #if !WIN32
12768     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12769     FILE *commandOutput;
12770     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12771     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12772     int nBuffers;
12773     int i;
12774     int archived;
12775     char *arcDir;
12776
12777     if (! cmailMsgLoaded) {
12778         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12779         return;
12780     }
12781
12782     if (nCmailGames == nCmailResults) {
12783         DisplayError(_("No unfinished games"), 0);
12784         return;
12785     }
12786
12787 #if CMAIL_PROHIBIT_REMAIL
12788     if (cmailMailedMove) {
12789       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);
12790         DisplayError(msg, 0);
12791         return;
12792     }
12793 #endif
12794
12795     if (! (cmailMailedMove || RegisterMove())) return;
12796
12797     if (   cmailMailedMove
12798         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12799       snprintf(string, MSG_SIZ, partCommandString,
12800                appData.debugMode ? " -v" : "", appData.cmailGameName);
12801         commandOutput = popen(string, "r");
12802
12803         if (commandOutput == NULL) {
12804             DisplayError(_("Failed to invoke cmail"), 0);
12805         } else {
12806             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12807                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12808             }
12809             if (nBuffers > 1) {
12810                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12811                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12812                 nBytes = MSG_SIZ - 1;
12813             } else {
12814                 (void) memcpy(msg, buffer, nBytes);
12815             }
12816             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12817
12818             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12819                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12820
12821                 archived = TRUE;
12822                 for (i = 0; i < nCmailGames; i ++) {
12823                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12824                         archived = FALSE;
12825                     }
12826                 }
12827                 if (   archived
12828                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12829                         != NULL)) {
12830                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12831                            arcDir,
12832                            appData.cmailGameName,
12833                            gameInfo.date);
12834                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12835                     cmailMsgLoaded = FALSE;
12836                 }
12837             }
12838
12839             DisplayInformation(msg);
12840             pclose(commandOutput);
12841         }
12842     } else {
12843         if ((*cmailMsg) != '\0') {
12844             DisplayInformation(cmailMsg);
12845         }
12846     }
12847
12848     return;
12849 #endif /* !WIN32 */
12850 }
12851
12852 char *
12853 CmailMsg()
12854 {
12855 #if WIN32
12856     return NULL;
12857 #else
12858     int  prependComma = 0;
12859     char number[5];
12860     char string[MSG_SIZ];       /* Space for game-list */
12861     int  i;
12862
12863     if (!cmailMsgLoaded) return "";
12864
12865     if (cmailMailedMove) {
12866       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12867     } else {
12868         /* Create a list of games left */
12869       snprintf(string, MSG_SIZ, "[");
12870         for (i = 0; i < nCmailGames; i ++) {
12871             if (! (   cmailMoveRegistered[i]
12872                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12873                 if (prependComma) {
12874                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12875                 } else {
12876                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12877                     prependComma = 1;
12878                 }
12879
12880                 strcat(string, number);
12881             }
12882         }
12883         strcat(string, "]");
12884
12885         if (nCmailMovesRegistered + nCmailResults == 0) {
12886             switch (nCmailGames) {
12887               case 1:
12888                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12889                 break;
12890
12891               case 2:
12892                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12893                 break;
12894
12895               default:
12896                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12897                          nCmailGames);
12898                 break;
12899             }
12900         } else {
12901             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12902               case 1:
12903                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12904                          string);
12905                 break;
12906
12907               case 0:
12908                 if (nCmailResults == nCmailGames) {
12909                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12910                 } else {
12911                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12912                 }
12913                 break;
12914
12915               default:
12916                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12917                          string);
12918             }
12919         }
12920     }
12921     return cmailMsg;
12922 #endif /* WIN32 */
12923 }
12924
12925 void
12926 ResetGameEvent()
12927 {
12928     if (gameMode == Training)
12929       SetTrainingModeOff();
12930
12931     Reset(TRUE, TRUE);
12932     cmailMsgLoaded = FALSE;
12933     if (appData.icsActive) {
12934       SendToICS(ics_prefix);
12935       SendToICS("refresh\n");
12936     }
12937 }
12938
12939 void
12940 ExitEvent(status)
12941      int status;
12942 {
12943     exiting++;
12944     if (exiting > 2) {
12945       /* Give up on clean exit */
12946       exit(status);
12947     }
12948     if (exiting > 1) {
12949       /* Keep trying for clean exit */
12950       return;
12951     }
12952
12953     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12954
12955     if (telnetISR != NULL) {
12956       RemoveInputSource(telnetISR);
12957     }
12958     if (icsPR != NoProc) {
12959       DestroyChildProcess(icsPR, TRUE);
12960     }
12961
12962     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12963     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12964
12965     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12966     /* make sure this other one finishes before killing it!                  */
12967     if(endingGame) { int count = 0;
12968         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12969         while(endingGame && count++ < 10) DoSleep(1);
12970         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12971     }
12972
12973     /* Kill off chess programs */
12974     if (first.pr != NoProc) {
12975         ExitAnalyzeMode();
12976
12977         DoSleep( appData.delayBeforeQuit );
12978         SendToProgram("quit\n", &first);
12979         DoSleep( appData.delayAfterQuit );
12980         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12981     }
12982     if (second.pr != NoProc) {
12983         DoSleep( appData.delayBeforeQuit );
12984         SendToProgram("quit\n", &second);
12985         DoSleep( appData.delayAfterQuit );
12986         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12987     }
12988     if (first.isr != NULL) {
12989         RemoveInputSource(first.isr);
12990     }
12991     if (second.isr != NULL) {
12992         RemoveInputSource(second.isr);
12993     }
12994
12995     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12996     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12997
12998     ShutDownFrontEnd();
12999     exit(status);
13000 }
13001
13002 void
13003 PauseEvent()
13004 {
13005     if (appData.debugMode)
13006         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13007     if (pausing) {
13008         pausing = FALSE;
13009         ModeHighlight();
13010         if (gameMode == MachinePlaysWhite ||
13011             gameMode == MachinePlaysBlack) {
13012             StartClocks();
13013         } else {
13014             DisplayBothClocks();
13015         }
13016         if (gameMode == PlayFromGameFile) {
13017             if (appData.timeDelay >= 0)
13018                 AutoPlayGameLoop();
13019         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13020             Reset(FALSE, TRUE);
13021             SendToICS(ics_prefix);
13022             SendToICS("refresh\n");
13023         } else if (currentMove < forwardMostMove) {
13024             ForwardInner(forwardMostMove);
13025         }
13026         pauseExamInvalid = FALSE;
13027     } else {
13028         switch (gameMode) {
13029           default:
13030             return;
13031           case IcsExamining:
13032             pauseExamForwardMostMove = forwardMostMove;
13033             pauseExamInvalid = FALSE;
13034             /* fall through */
13035           case IcsObserving:
13036           case IcsPlayingWhite:
13037           case IcsPlayingBlack:
13038             pausing = TRUE;
13039             ModeHighlight();
13040             return;
13041           case PlayFromGameFile:
13042             (void) StopLoadGameTimer();
13043             pausing = TRUE;
13044             ModeHighlight();
13045             break;
13046           case BeginningOfGame:
13047             if (appData.icsActive) return;
13048             /* else fall through */
13049           case MachinePlaysWhite:
13050           case MachinePlaysBlack:
13051           case TwoMachinesPlay:
13052             if (forwardMostMove == 0)
13053               return;           /* don't pause if no one has moved */
13054             if ((gameMode == MachinePlaysWhite &&
13055                  !WhiteOnMove(forwardMostMove)) ||
13056                 (gameMode == MachinePlaysBlack &&
13057                  WhiteOnMove(forwardMostMove))) {
13058                 StopClocks();
13059             }
13060             pausing = TRUE;
13061             ModeHighlight();
13062             break;
13063         }
13064     }
13065 }
13066
13067 void
13068 EditCommentEvent()
13069 {
13070     char title[MSG_SIZ];
13071
13072     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13073       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13074     } else {
13075       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13076                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13077                parseList[currentMove - 1]);
13078     }
13079
13080     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13081 }
13082
13083
13084 void
13085 EditTagsEvent()
13086 {
13087     char *tags = PGNTags(&gameInfo);
13088     bookUp = FALSE;
13089     EditTagsPopUp(tags, NULL);
13090     free(tags);
13091 }
13092
13093 void
13094 AnalyzeModeEvent()
13095 {
13096     if (appData.noChessProgram || gameMode == AnalyzeMode)
13097       return;
13098
13099     if (gameMode != AnalyzeFile) {
13100         if (!appData.icsEngineAnalyze) {
13101                EditGameEvent();
13102                if (gameMode != EditGame) return;
13103         }
13104         ResurrectChessProgram();
13105         SendToProgram("analyze\n", &first);
13106         first.analyzing = TRUE;
13107         /*first.maybeThinking = TRUE;*/
13108         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13109         EngineOutputPopUp();
13110     }
13111     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13112     pausing = FALSE;
13113     ModeHighlight();
13114     SetGameInfo();
13115
13116     StartAnalysisClock();
13117     GetTimeMark(&lastNodeCountTime);
13118     lastNodeCount = 0;
13119 }
13120
13121 void
13122 AnalyzeFileEvent()
13123 {
13124     if (appData.noChessProgram || gameMode == AnalyzeFile)
13125       return;
13126
13127     if (gameMode != AnalyzeMode) {
13128         EditGameEvent();
13129         if (gameMode != EditGame) return;
13130         ResurrectChessProgram();
13131         SendToProgram("analyze\n", &first);
13132         first.analyzing = TRUE;
13133         /*first.maybeThinking = TRUE;*/
13134         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13135         EngineOutputPopUp();
13136     }
13137     gameMode = AnalyzeFile;
13138     pausing = FALSE;
13139     ModeHighlight();
13140     SetGameInfo();
13141
13142     StartAnalysisClock();
13143     GetTimeMark(&lastNodeCountTime);
13144     lastNodeCount = 0;
13145     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13146 }
13147
13148 void
13149 MachineWhiteEvent()
13150 {
13151     char buf[MSG_SIZ];
13152     char *bookHit = NULL;
13153
13154     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13155       return;
13156
13157
13158     if (gameMode == PlayFromGameFile ||
13159         gameMode == TwoMachinesPlay  ||
13160         gameMode == Training         ||
13161         gameMode == AnalyzeMode      ||
13162         gameMode == EndOfGame)
13163         EditGameEvent();
13164
13165     if (gameMode == EditPosition)
13166         EditPositionDone(TRUE);
13167
13168     if (!WhiteOnMove(currentMove)) {
13169         DisplayError(_("It is not White's turn"), 0);
13170         return;
13171     }
13172
13173     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13174       ExitAnalyzeMode();
13175
13176     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13177         gameMode == AnalyzeFile)
13178         TruncateGame();
13179
13180     ResurrectChessProgram();    /* in case it isn't running */
13181     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13182         gameMode = MachinePlaysWhite;
13183         ResetClocks();
13184     } else
13185     gameMode = MachinePlaysWhite;
13186     pausing = FALSE;
13187     ModeHighlight();
13188     SetGameInfo();
13189     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13190     DisplayTitle(buf);
13191     if (first.sendName) {
13192       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13193       SendToProgram(buf, &first);
13194     }
13195     if (first.sendTime) {
13196       if (first.useColors) {
13197         SendToProgram("black\n", &first); /*gnu kludge*/
13198       }
13199       SendTimeRemaining(&first, TRUE);
13200     }
13201     if (first.useColors) {
13202       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13203     }
13204     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13205     SetMachineThinkingEnables();
13206     first.maybeThinking = TRUE;
13207     StartClocks();
13208     firstMove = FALSE;
13209
13210     if (appData.autoFlipView && !flipView) {
13211       flipView = !flipView;
13212       DrawPosition(FALSE, NULL);
13213       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13214     }
13215
13216     if(bookHit) { // [HGM] book: simulate book reply
13217         static char bookMove[MSG_SIZ]; // a bit generous?
13218
13219         programStats.nodes = programStats.depth = programStats.time =
13220         programStats.score = programStats.got_only_move = 0;
13221         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13222
13223         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13224         strcat(bookMove, bookHit);
13225         HandleMachineMove(bookMove, &first);
13226     }
13227 }
13228
13229 void
13230 MachineBlackEvent()
13231 {
13232   char buf[MSG_SIZ];
13233   char *bookHit = NULL;
13234
13235     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13236         return;
13237
13238
13239     if (gameMode == PlayFromGameFile ||
13240         gameMode == TwoMachinesPlay  ||
13241         gameMode == Training         ||
13242         gameMode == AnalyzeMode      ||
13243         gameMode == EndOfGame)
13244         EditGameEvent();
13245
13246     if (gameMode == EditPosition)
13247         EditPositionDone(TRUE);
13248
13249     if (WhiteOnMove(currentMove)) {
13250         DisplayError(_("It is not Black's turn"), 0);
13251         return;
13252     }
13253
13254     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13255       ExitAnalyzeMode();
13256
13257     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13258         gameMode == AnalyzeFile)
13259         TruncateGame();
13260
13261     ResurrectChessProgram();    /* in case it isn't running */
13262     gameMode = MachinePlaysBlack;
13263     pausing = FALSE;
13264     ModeHighlight();
13265     SetGameInfo();
13266     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13267     DisplayTitle(buf);
13268     if (first.sendName) {
13269       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13270       SendToProgram(buf, &first);
13271     }
13272     if (first.sendTime) {
13273       if (first.useColors) {
13274         SendToProgram("white\n", &first); /*gnu kludge*/
13275       }
13276       SendTimeRemaining(&first, FALSE);
13277     }
13278     if (first.useColors) {
13279       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13280     }
13281     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13282     SetMachineThinkingEnables();
13283     first.maybeThinking = TRUE;
13284     StartClocks();
13285
13286     if (appData.autoFlipView && flipView) {
13287       flipView = !flipView;
13288       DrawPosition(FALSE, NULL);
13289       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13290     }
13291     if(bookHit) { // [HGM] book: simulate book reply
13292         static char bookMove[MSG_SIZ]; // a bit generous?
13293
13294         programStats.nodes = programStats.depth = programStats.time =
13295         programStats.score = programStats.got_only_move = 0;
13296         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13297
13298         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13299         strcat(bookMove, bookHit);
13300         HandleMachineMove(bookMove, &first);
13301     }
13302 }
13303
13304
13305 void
13306 DisplayTwoMachinesTitle()
13307 {
13308     char buf[MSG_SIZ];
13309     if (appData.matchGames > 0) {
13310         if(appData.tourneyFile[0]) {
13311           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13312                    gameInfo.white, gameInfo.black,
13313                    nextGame+1, appData.matchGames+1,
13314                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13315         } else 
13316         if (first.twoMachinesColor[0] == 'w') {
13317           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13318                    gameInfo.white, gameInfo.black,
13319                    first.matchWins, second.matchWins,
13320                    matchGame - 1 - (first.matchWins + second.matchWins));
13321         } else {
13322           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13323                    gameInfo.white, gameInfo.black,
13324                    second.matchWins, first.matchWins,
13325                    matchGame - 1 - (first.matchWins + second.matchWins));
13326         }
13327     } else {
13328       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13329     }
13330     DisplayTitle(buf);
13331 }
13332
13333 void
13334 SettingsMenuIfReady()
13335 {
13336   if (second.lastPing != second.lastPong) {
13337     DisplayMessage("", _("Waiting for second chess program"));
13338     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13339     return;
13340   }
13341   ThawUI();
13342   DisplayMessage("", "");
13343   SettingsPopUp(&second);
13344 }
13345
13346 int
13347 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13348 {
13349     char buf[MSG_SIZ];
13350     if (cps->pr == NoProc) {
13351         StartChessProgram(cps);
13352         if (cps->protocolVersion == 1) {
13353           retry();
13354         } else {
13355           /* kludge: allow timeout for initial "feature" command */
13356           FreezeUI();
13357           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13358           DisplayMessage("", buf);
13359           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13360         }
13361         return 1;
13362     }
13363     return 0;
13364 }
13365
13366 void
13367 TwoMachinesEvent P((void))
13368 {
13369     int i;
13370     char buf[MSG_SIZ];
13371     ChessProgramState *onmove;
13372     char *bookHit = NULL;
13373     static int stalling = 0;
13374     TimeMark now;
13375     long wait;
13376
13377     if (appData.noChessProgram) return;
13378
13379     switch (gameMode) {
13380       case TwoMachinesPlay:
13381         return;
13382       case MachinePlaysWhite:
13383       case MachinePlaysBlack:
13384         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13385             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13386             return;
13387         }
13388         /* fall through */
13389       case BeginningOfGame:
13390       case PlayFromGameFile:
13391       case EndOfGame:
13392         EditGameEvent();
13393         if (gameMode != EditGame) return;
13394         break;
13395       case EditPosition:
13396         EditPositionDone(TRUE);
13397         break;
13398       case AnalyzeMode:
13399       case AnalyzeFile:
13400         ExitAnalyzeMode();
13401         break;
13402       case EditGame:
13403       default:
13404         break;
13405     }
13406
13407 //    forwardMostMove = currentMove;
13408     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13409
13410     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13411
13412     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13413     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13414       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13415       return;
13416     }
13417     if(!stalling) {
13418       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13419       SendToProgram("force\n", &second);
13420       stalling = 1;
13421       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13422       return;
13423     }
13424     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13425     if(appData.matchPause>10000 || appData.matchPause<10)
13426                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13427     wait = SubtractTimeMarks(&now, &pauseStart);
13428     if(wait < appData.matchPause) {
13429         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13430         return;
13431     }
13432     stalling = 0;
13433     DisplayMessage("", "");
13434     if (startedFromSetupPosition) {
13435         SendBoard(&second, backwardMostMove);
13436     if (appData.debugMode) {
13437         fprintf(debugFP, "Two Machines\n");
13438     }
13439     }
13440     for (i = backwardMostMove; i < forwardMostMove; i++) {
13441         SendMoveToProgram(i, &second);
13442     }
13443
13444     gameMode = TwoMachinesPlay;
13445     pausing = FALSE;
13446     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13447     SetGameInfo();
13448     DisplayTwoMachinesTitle();
13449     firstMove = TRUE;
13450     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13451         onmove = &first;
13452     } else {
13453         onmove = &second;
13454     }
13455     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13456     SendToProgram(first.computerString, &first);
13457     if (first.sendName) {
13458       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13459       SendToProgram(buf, &first);
13460     }
13461     SendToProgram(second.computerString, &second);
13462     if (second.sendName) {
13463       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13464       SendToProgram(buf, &second);
13465     }
13466
13467     ResetClocks();
13468     if (!first.sendTime || !second.sendTime) {
13469         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13470         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13471     }
13472     if (onmove->sendTime) {
13473       if (onmove->useColors) {
13474         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13475       }
13476       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13477     }
13478     if (onmove->useColors) {
13479       SendToProgram(onmove->twoMachinesColor, onmove);
13480     }
13481     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13482 //    SendToProgram("go\n", onmove);
13483     onmove->maybeThinking = TRUE;
13484     SetMachineThinkingEnables();
13485
13486     StartClocks();
13487
13488     if(bookHit) { // [HGM] book: simulate book reply
13489         static char bookMove[MSG_SIZ]; // a bit generous?
13490
13491         programStats.nodes = programStats.depth = programStats.time =
13492         programStats.score = programStats.got_only_move = 0;
13493         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13494
13495         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13496         strcat(bookMove, bookHit);
13497         savedMessage = bookMove; // args for deferred call
13498         savedState = onmove;
13499         ScheduleDelayedEvent(DeferredBookMove, 1);
13500     }
13501 }
13502
13503 void
13504 TrainingEvent()
13505 {
13506     if (gameMode == Training) {
13507       SetTrainingModeOff();
13508       gameMode = PlayFromGameFile;
13509       DisplayMessage("", _("Training mode off"));
13510     } else {
13511       gameMode = Training;
13512       animateTraining = appData.animate;
13513
13514       /* make sure we are not already at the end of the game */
13515       if (currentMove < forwardMostMove) {
13516         SetTrainingModeOn();
13517         DisplayMessage("", _("Training mode on"));
13518       } else {
13519         gameMode = PlayFromGameFile;
13520         DisplayError(_("Already at end of game"), 0);
13521       }
13522     }
13523     ModeHighlight();
13524 }
13525
13526 void
13527 IcsClientEvent()
13528 {
13529     if (!appData.icsActive) return;
13530     switch (gameMode) {
13531       case IcsPlayingWhite:
13532       case IcsPlayingBlack:
13533       case IcsObserving:
13534       case IcsIdle:
13535       case BeginningOfGame:
13536       case IcsExamining:
13537         return;
13538
13539       case EditGame:
13540         break;
13541
13542       case EditPosition:
13543         EditPositionDone(TRUE);
13544         break;
13545
13546       case AnalyzeMode:
13547       case AnalyzeFile:
13548         ExitAnalyzeMode();
13549         break;
13550
13551       default:
13552         EditGameEvent();
13553         break;
13554     }
13555
13556     gameMode = IcsIdle;
13557     ModeHighlight();
13558     return;
13559 }
13560
13561
13562 void
13563 EditGameEvent()
13564 {
13565     int i;
13566
13567     switch (gameMode) {
13568       case Training:
13569         SetTrainingModeOff();
13570         break;
13571       case MachinePlaysWhite:
13572       case MachinePlaysBlack:
13573       case BeginningOfGame:
13574         SendToProgram("force\n", &first);
13575         SetUserThinkingEnables();
13576         break;
13577       case PlayFromGameFile:
13578         (void) StopLoadGameTimer();
13579         if (gameFileFP != NULL) {
13580             gameFileFP = NULL;
13581         }
13582         break;
13583       case EditPosition:
13584         EditPositionDone(TRUE);
13585         break;
13586       case AnalyzeMode:
13587       case AnalyzeFile:
13588         ExitAnalyzeMode();
13589         SendToProgram("force\n", &first);
13590         break;
13591       case TwoMachinesPlay:
13592         GameEnds(EndOfFile, NULL, GE_PLAYER);
13593         ResurrectChessProgram();
13594         SetUserThinkingEnables();
13595         break;
13596       case EndOfGame:
13597         ResurrectChessProgram();
13598         break;
13599       case IcsPlayingBlack:
13600       case IcsPlayingWhite:
13601         DisplayError(_("Warning: You are still playing a game"), 0);
13602         break;
13603       case IcsObserving:
13604         DisplayError(_("Warning: You are still observing a game"), 0);
13605         break;
13606       case IcsExamining:
13607         DisplayError(_("Warning: You are still examining a game"), 0);
13608         break;
13609       case IcsIdle:
13610         break;
13611       case EditGame:
13612       default:
13613         return;
13614     }
13615
13616     pausing = FALSE;
13617     StopClocks();
13618     first.offeredDraw = second.offeredDraw = 0;
13619
13620     if (gameMode == PlayFromGameFile) {
13621         whiteTimeRemaining = timeRemaining[0][currentMove];
13622         blackTimeRemaining = timeRemaining[1][currentMove];
13623         DisplayTitle("");
13624     }
13625
13626     if (gameMode == MachinePlaysWhite ||
13627         gameMode == MachinePlaysBlack ||
13628         gameMode == TwoMachinesPlay ||
13629         gameMode == EndOfGame) {
13630         i = forwardMostMove;
13631         while (i > currentMove) {
13632             SendToProgram("undo\n", &first);
13633             i--;
13634         }
13635         if(!adjustedClock) {
13636         whiteTimeRemaining = timeRemaining[0][currentMove];
13637         blackTimeRemaining = timeRemaining[1][currentMove];
13638         DisplayBothClocks();
13639         }
13640         if (whiteFlag || blackFlag) {
13641             whiteFlag = blackFlag = 0;
13642         }
13643         DisplayTitle("");
13644     }
13645
13646     gameMode = EditGame;
13647     ModeHighlight();
13648     SetGameInfo();
13649 }
13650
13651
13652 void
13653 EditPositionEvent()
13654 {
13655     if (gameMode == EditPosition) {
13656         EditGameEvent();
13657         return;
13658     }
13659
13660     EditGameEvent();
13661     if (gameMode != EditGame) return;
13662
13663     gameMode = EditPosition;
13664     ModeHighlight();
13665     SetGameInfo();
13666     if (currentMove > 0)
13667       CopyBoard(boards[0], boards[currentMove]);
13668
13669     blackPlaysFirst = !WhiteOnMove(currentMove);
13670     ResetClocks();
13671     currentMove = forwardMostMove = backwardMostMove = 0;
13672     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13673     DisplayMove(-1);
13674 }
13675
13676 void
13677 ExitAnalyzeMode()
13678 {
13679     /* [DM] icsEngineAnalyze - possible call from other functions */
13680     if (appData.icsEngineAnalyze) {
13681         appData.icsEngineAnalyze = FALSE;
13682
13683         DisplayMessage("",_("Close ICS engine analyze..."));
13684     }
13685     if (first.analysisSupport && first.analyzing) {
13686       SendToProgram("exit\n", &first);
13687       first.analyzing = FALSE;
13688     }
13689     thinkOutput[0] = NULLCHAR;
13690 }
13691
13692 void
13693 EditPositionDone(Boolean fakeRights)
13694 {
13695     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13696
13697     startedFromSetupPosition = TRUE;
13698     InitChessProgram(&first, FALSE);
13699     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13700       boards[0][EP_STATUS] = EP_NONE;
13701       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13702     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13703         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13704         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13705       } else boards[0][CASTLING][2] = NoRights;
13706     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13707         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13708         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13709       } else boards[0][CASTLING][5] = NoRights;
13710     }
13711     SendToProgram("force\n", &first);
13712     if (blackPlaysFirst) {
13713         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13714         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13715         currentMove = forwardMostMove = backwardMostMove = 1;
13716         CopyBoard(boards[1], boards[0]);
13717     } else {
13718         currentMove = forwardMostMove = backwardMostMove = 0;
13719     }
13720     SendBoard(&first, forwardMostMove);
13721     if (appData.debugMode) {
13722         fprintf(debugFP, "EditPosDone\n");
13723     }
13724     DisplayTitle("");
13725     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13726     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13727     gameMode = EditGame;
13728     ModeHighlight();
13729     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13730     ClearHighlights(); /* [AS] */
13731 }
13732
13733 /* Pause for `ms' milliseconds */
13734 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13735 void
13736 TimeDelay(ms)
13737      long ms;
13738 {
13739     TimeMark m1, m2;
13740
13741     GetTimeMark(&m1);
13742     do {
13743         GetTimeMark(&m2);
13744     } while (SubtractTimeMarks(&m2, &m1) < ms);
13745 }
13746
13747 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13748 void
13749 SendMultiLineToICS(buf)
13750      char *buf;
13751 {
13752     char temp[MSG_SIZ+1], *p;
13753     int len;
13754
13755     len = strlen(buf);
13756     if (len > MSG_SIZ)
13757       len = MSG_SIZ;
13758
13759     strncpy(temp, buf, len);
13760     temp[len] = 0;
13761
13762     p = temp;
13763     while (*p) {
13764         if (*p == '\n' || *p == '\r')
13765           *p = ' ';
13766         ++p;
13767     }
13768
13769     strcat(temp, "\n");
13770     SendToICS(temp);
13771     SendToPlayer(temp, strlen(temp));
13772 }
13773
13774 void
13775 SetWhiteToPlayEvent()
13776 {
13777     if (gameMode == EditPosition) {
13778         blackPlaysFirst = FALSE;
13779         DisplayBothClocks();    /* works because currentMove is 0 */
13780     } else if (gameMode == IcsExamining) {
13781         SendToICS(ics_prefix);
13782         SendToICS("tomove white\n");
13783     }
13784 }
13785
13786 void
13787 SetBlackToPlayEvent()
13788 {
13789     if (gameMode == EditPosition) {
13790         blackPlaysFirst = TRUE;
13791         currentMove = 1;        /* kludge */
13792         DisplayBothClocks();
13793         currentMove = 0;
13794     } else if (gameMode == IcsExamining) {
13795         SendToICS(ics_prefix);
13796         SendToICS("tomove black\n");
13797     }
13798 }
13799
13800 void
13801 EditPositionMenuEvent(selection, x, y)
13802      ChessSquare selection;
13803      int x, y;
13804 {
13805     char buf[MSG_SIZ];
13806     ChessSquare piece = boards[0][y][x];
13807
13808     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13809
13810     switch (selection) {
13811       case ClearBoard:
13812         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13813             SendToICS(ics_prefix);
13814             SendToICS("bsetup clear\n");
13815         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13816             SendToICS(ics_prefix);
13817             SendToICS("clearboard\n");
13818         } else {
13819             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13820                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13821                 for (y = 0; y < BOARD_HEIGHT; y++) {
13822                     if (gameMode == IcsExamining) {
13823                         if (boards[currentMove][y][x] != EmptySquare) {
13824                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13825                                     AAA + x, ONE + y);
13826                             SendToICS(buf);
13827                         }
13828                     } else {
13829                         boards[0][y][x] = p;
13830                     }
13831                 }
13832             }
13833         }
13834         if (gameMode == EditPosition) {
13835             DrawPosition(FALSE, boards[0]);
13836         }
13837         break;
13838
13839       case WhitePlay:
13840         SetWhiteToPlayEvent();
13841         break;
13842
13843       case BlackPlay:
13844         SetBlackToPlayEvent();
13845         break;
13846
13847       case EmptySquare:
13848         if (gameMode == IcsExamining) {
13849             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13850             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13851             SendToICS(buf);
13852         } else {
13853             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13854                 if(x == BOARD_LEFT-2) {
13855                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13856                     boards[0][y][1] = 0;
13857                 } else
13858                 if(x == BOARD_RGHT+1) {
13859                     if(y >= gameInfo.holdingsSize) break;
13860                     boards[0][y][BOARD_WIDTH-2] = 0;
13861                 } else break;
13862             }
13863             boards[0][y][x] = EmptySquare;
13864             DrawPosition(FALSE, boards[0]);
13865         }
13866         break;
13867
13868       case PromotePiece:
13869         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13870            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13871             selection = (ChessSquare) (PROMOTED piece);
13872         } else if(piece == EmptySquare) selection = WhiteSilver;
13873         else selection = (ChessSquare)((int)piece - 1);
13874         goto defaultlabel;
13875
13876       case DemotePiece:
13877         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13878            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13879             selection = (ChessSquare) (DEMOTED piece);
13880         } else if(piece == EmptySquare) selection = BlackSilver;
13881         else selection = (ChessSquare)((int)piece + 1);
13882         goto defaultlabel;
13883
13884       case WhiteQueen:
13885       case BlackQueen:
13886         if(gameInfo.variant == VariantShatranj ||
13887            gameInfo.variant == VariantXiangqi  ||
13888            gameInfo.variant == VariantCourier  ||
13889            gameInfo.variant == VariantMakruk     )
13890             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13891         goto defaultlabel;
13892
13893       case WhiteKing:
13894       case BlackKing:
13895         if(gameInfo.variant == VariantXiangqi)
13896             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13897         if(gameInfo.variant == VariantKnightmate)
13898             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13899       default:
13900         defaultlabel:
13901         if (gameMode == IcsExamining) {
13902             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13903             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13904                      PieceToChar(selection), AAA + x, ONE + y);
13905             SendToICS(buf);
13906         } else {
13907             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13908                 int n;
13909                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13910                     n = PieceToNumber(selection - BlackPawn);
13911                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13912                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13913                     boards[0][BOARD_HEIGHT-1-n][1]++;
13914                 } else
13915                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13916                     n = PieceToNumber(selection);
13917                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13918                     boards[0][n][BOARD_WIDTH-1] = selection;
13919                     boards[0][n][BOARD_WIDTH-2]++;
13920                 }
13921             } else
13922             boards[0][y][x] = selection;
13923             DrawPosition(TRUE, boards[0]);
13924         }
13925         break;
13926     }
13927 }
13928
13929
13930 void
13931 DropMenuEvent(selection, x, y)
13932      ChessSquare selection;
13933      int x, y;
13934 {
13935     ChessMove moveType;
13936
13937     switch (gameMode) {
13938       case IcsPlayingWhite:
13939       case MachinePlaysBlack:
13940         if (!WhiteOnMove(currentMove)) {
13941             DisplayMoveError(_("It is Black's turn"));
13942             return;
13943         }
13944         moveType = WhiteDrop;
13945         break;
13946       case IcsPlayingBlack:
13947       case MachinePlaysWhite:
13948         if (WhiteOnMove(currentMove)) {
13949             DisplayMoveError(_("It is White's turn"));
13950             return;
13951         }
13952         moveType = BlackDrop;
13953         break;
13954       case EditGame:
13955         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13956         break;
13957       default:
13958         return;
13959     }
13960
13961     if (moveType == BlackDrop && selection < BlackPawn) {
13962       selection = (ChessSquare) ((int) selection
13963                                  + (int) BlackPawn - (int) WhitePawn);
13964     }
13965     if (boards[currentMove][y][x] != EmptySquare) {
13966         DisplayMoveError(_("That square is occupied"));
13967         return;
13968     }
13969
13970     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13971 }
13972
13973 void
13974 AcceptEvent()
13975 {
13976     /* Accept a pending offer of any kind from opponent */
13977
13978     if (appData.icsActive) {
13979         SendToICS(ics_prefix);
13980         SendToICS("accept\n");
13981     } else if (cmailMsgLoaded) {
13982         if (currentMove == cmailOldMove &&
13983             commentList[cmailOldMove] != NULL &&
13984             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13985                    "Black offers a draw" : "White offers a draw")) {
13986             TruncateGame();
13987             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13988             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13989         } else {
13990             DisplayError(_("There is no pending offer on this move"), 0);
13991             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13992         }
13993     } else {
13994         /* Not used for offers from chess program */
13995     }
13996 }
13997
13998 void
13999 DeclineEvent()
14000 {
14001     /* Decline a pending offer of any kind from opponent */
14002
14003     if (appData.icsActive) {
14004         SendToICS(ics_prefix);
14005         SendToICS("decline\n");
14006     } else if (cmailMsgLoaded) {
14007         if (currentMove == cmailOldMove &&
14008             commentList[cmailOldMove] != NULL &&
14009             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14010                    "Black offers a draw" : "White offers a draw")) {
14011 #ifdef NOTDEF
14012             AppendComment(cmailOldMove, "Draw declined", TRUE);
14013             DisplayComment(cmailOldMove - 1, "Draw declined");
14014 #endif /*NOTDEF*/
14015         } else {
14016             DisplayError(_("There is no pending offer on this move"), 0);
14017         }
14018     } else {
14019         /* Not used for offers from chess program */
14020     }
14021 }
14022
14023 void
14024 RematchEvent()
14025 {
14026     /* Issue ICS rematch command */
14027     if (appData.icsActive) {
14028         SendToICS(ics_prefix);
14029         SendToICS("rematch\n");
14030     }
14031 }
14032
14033 void
14034 CallFlagEvent()
14035 {
14036     /* Call your opponent's flag (claim a win on time) */
14037     if (appData.icsActive) {
14038         SendToICS(ics_prefix);
14039         SendToICS("flag\n");
14040     } else {
14041         switch (gameMode) {
14042           default:
14043             return;
14044           case MachinePlaysWhite:
14045             if (whiteFlag) {
14046                 if (blackFlag)
14047                   GameEnds(GameIsDrawn, "Both players ran out of time",
14048                            GE_PLAYER);
14049                 else
14050                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14051             } else {
14052                 DisplayError(_("Your opponent is not out of time"), 0);
14053             }
14054             break;
14055           case MachinePlaysBlack:
14056             if (blackFlag) {
14057                 if (whiteFlag)
14058                   GameEnds(GameIsDrawn, "Both players ran out of time",
14059                            GE_PLAYER);
14060                 else
14061                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14062             } else {
14063                 DisplayError(_("Your opponent is not out of time"), 0);
14064             }
14065             break;
14066         }
14067     }
14068 }
14069
14070 void
14071 ClockClick(int which)
14072 {       // [HGM] code moved to back-end from winboard.c
14073         if(which) { // black clock
14074           if (gameMode == EditPosition || gameMode == IcsExamining) {
14075             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14076             SetBlackToPlayEvent();
14077           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14078           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14079           } else if (shiftKey) {
14080             AdjustClock(which, -1);
14081           } else if (gameMode == IcsPlayingWhite ||
14082                      gameMode == MachinePlaysBlack) {
14083             CallFlagEvent();
14084           }
14085         } else { // white clock
14086           if (gameMode == EditPosition || gameMode == IcsExamining) {
14087             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14088             SetWhiteToPlayEvent();
14089           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14090           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14091           } else if (shiftKey) {
14092             AdjustClock(which, -1);
14093           } else if (gameMode == IcsPlayingBlack ||
14094                    gameMode == MachinePlaysWhite) {
14095             CallFlagEvent();
14096           }
14097         }
14098 }
14099
14100 void
14101 DrawEvent()
14102 {
14103     /* Offer draw or accept pending draw offer from opponent */
14104
14105     if (appData.icsActive) {
14106         /* Note: tournament rules require draw offers to be
14107            made after you make your move but before you punch
14108            your clock.  Currently ICS doesn't let you do that;
14109            instead, you immediately punch your clock after making
14110            a move, but you can offer a draw at any time. */
14111
14112         SendToICS(ics_prefix);
14113         SendToICS("draw\n");
14114         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14115     } else if (cmailMsgLoaded) {
14116         if (currentMove == cmailOldMove &&
14117             commentList[cmailOldMove] != NULL &&
14118             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14119                    "Black offers a draw" : "White offers a draw")) {
14120             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14121             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14122         } else if (currentMove == cmailOldMove + 1) {
14123             char *offer = WhiteOnMove(cmailOldMove) ?
14124               "White offers a draw" : "Black offers a draw";
14125             AppendComment(currentMove, offer, TRUE);
14126             DisplayComment(currentMove - 1, offer);
14127             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14128         } else {
14129             DisplayError(_("You must make your move before offering a draw"), 0);
14130             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14131         }
14132     } else if (first.offeredDraw) {
14133         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14134     } else {
14135         if (first.sendDrawOffers) {
14136             SendToProgram("draw\n", &first);
14137             userOfferedDraw = TRUE;
14138         }
14139     }
14140 }
14141
14142 void
14143 AdjournEvent()
14144 {
14145     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14146
14147     if (appData.icsActive) {
14148         SendToICS(ics_prefix);
14149         SendToICS("adjourn\n");
14150     } else {
14151         /* Currently GNU Chess doesn't offer or accept Adjourns */
14152     }
14153 }
14154
14155
14156 void
14157 AbortEvent()
14158 {
14159     /* Offer Abort or accept pending Abort offer from opponent */
14160
14161     if (appData.icsActive) {
14162         SendToICS(ics_prefix);
14163         SendToICS("abort\n");
14164     } else {
14165         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14166     }
14167 }
14168
14169 void
14170 ResignEvent()
14171 {
14172     /* Resign.  You can do this even if it's not your turn. */
14173
14174     if (appData.icsActive) {
14175         SendToICS(ics_prefix);
14176         SendToICS("resign\n");
14177     } else {
14178         switch (gameMode) {
14179           case MachinePlaysWhite:
14180             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14181             break;
14182           case MachinePlaysBlack:
14183             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14184             break;
14185           case EditGame:
14186             if (cmailMsgLoaded) {
14187                 TruncateGame();
14188                 if (WhiteOnMove(cmailOldMove)) {
14189                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14190                 } else {
14191                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14192                 }
14193                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14194             }
14195             break;
14196           default:
14197             break;
14198         }
14199     }
14200 }
14201
14202
14203 void
14204 StopObservingEvent()
14205 {
14206     /* Stop observing current games */
14207     SendToICS(ics_prefix);
14208     SendToICS("unobserve\n");
14209 }
14210
14211 void
14212 StopExaminingEvent()
14213 {
14214     /* Stop observing current game */
14215     SendToICS(ics_prefix);
14216     SendToICS("unexamine\n");
14217 }
14218
14219 void
14220 ForwardInner(target)
14221      int target;
14222 {
14223     int limit;
14224
14225     if (appData.debugMode)
14226         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14227                 target, currentMove, forwardMostMove);
14228
14229     if (gameMode == EditPosition)
14230       return;
14231
14232     MarkTargetSquares(1);
14233
14234     if (gameMode == PlayFromGameFile && !pausing)
14235       PauseEvent();
14236
14237     if (gameMode == IcsExamining && pausing)
14238       limit = pauseExamForwardMostMove;
14239     else
14240       limit = forwardMostMove;
14241
14242     if (target > limit) target = limit;
14243
14244     if (target > 0 && moveList[target - 1][0]) {
14245         int fromX, fromY, toX, toY;
14246         toX = moveList[target - 1][2] - AAA;
14247         toY = moveList[target - 1][3] - ONE;
14248         if (moveList[target - 1][1] == '@') {
14249             if (appData.highlightLastMove) {
14250                 SetHighlights(-1, -1, toX, toY);
14251             }
14252         } else {
14253             fromX = moveList[target - 1][0] - AAA;
14254             fromY = moveList[target - 1][1] - ONE;
14255             if (target == currentMove + 1) {
14256                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14257             }
14258             if (appData.highlightLastMove) {
14259                 SetHighlights(fromX, fromY, toX, toY);
14260             }
14261         }
14262     }
14263     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14264         gameMode == Training || gameMode == PlayFromGameFile ||
14265         gameMode == AnalyzeFile) {
14266         while (currentMove < target) {
14267             SendMoveToProgram(currentMove++, &first);
14268         }
14269     } else {
14270         currentMove = target;
14271     }
14272
14273     if (gameMode == EditGame || gameMode == EndOfGame) {
14274         whiteTimeRemaining = timeRemaining[0][currentMove];
14275         blackTimeRemaining = timeRemaining[1][currentMove];
14276     }
14277     DisplayBothClocks();
14278     DisplayMove(currentMove - 1);
14279     DrawPosition(FALSE, boards[currentMove]);
14280     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14281     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14282         DisplayComment(currentMove - 1, commentList[currentMove]);
14283     }
14284 }
14285
14286
14287 void
14288 ForwardEvent()
14289 {
14290     if (gameMode == IcsExamining && !pausing) {
14291         SendToICS(ics_prefix);
14292         SendToICS("forward\n");
14293     } else {
14294         ForwardInner(currentMove + 1);
14295     }
14296 }
14297
14298 void
14299 ToEndEvent()
14300 {
14301     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14302         /* to optimze, we temporarily turn off analysis mode while we feed
14303          * the remaining moves to the engine. Otherwise we get analysis output
14304          * after each move.
14305          */
14306         if (first.analysisSupport) {
14307           SendToProgram("exit\nforce\n", &first);
14308           first.analyzing = FALSE;
14309         }
14310     }
14311
14312     if (gameMode == IcsExamining && !pausing) {
14313         SendToICS(ics_prefix);
14314         SendToICS("forward 999999\n");
14315     } else {
14316         ForwardInner(forwardMostMove);
14317     }
14318
14319     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14320         /* we have fed all the moves, so reactivate analysis mode */
14321         SendToProgram("analyze\n", &first);
14322         first.analyzing = TRUE;
14323         /*first.maybeThinking = TRUE;*/
14324         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14325     }
14326 }
14327
14328 void
14329 BackwardInner(target)
14330      int target;
14331 {
14332     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14333
14334     if (appData.debugMode)
14335         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14336                 target, currentMove, forwardMostMove);
14337
14338     if (gameMode == EditPosition) return;
14339     MarkTargetSquares(1);
14340     if (currentMove <= backwardMostMove) {
14341         ClearHighlights();
14342         DrawPosition(full_redraw, boards[currentMove]);
14343         return;
14344     }
14345     if (gameMode == PlayFromGameFile && !pausing)
14346       PauseEvent();
14347
14348     if (moveList[target][0]) {
14349         int fromX, fromY, toX, toY;
14350         toX = moveList[target][2] - AAA;
14351         toY = moveList[target][3] - ONE;
14352         if (moveList[target][1] == '@') {
14353             if (appData.highlightLastMove) {
14354                 SetHighlights(-1, -1, toX, toY);
14355             }
14356         } else {
14357             fromX = moveList[target][0] - AAA;
14358             fromY = moveList[target][1] - ONE;
14359             if (target == currentMove - 1) {
14360                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14361             }
14362             if (appData.highlightLastMove) {
14363                 SetHighlights(fromX, fromY, toX, toY);
14364             }
14365         }
14366     }
14367     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14368         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14369         while (currentMove > target) {
14370             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14371                 // null move cannot be undone. Reload program with move history before it.
14372                 int i;
14373                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14374                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14375                 }
14376                 SendBoard(&first, i); 
14377                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14378                 break;
14379             }
14380             SendToProgram("undo\n", &first);
14381             currentMove--;
14382         }
14383     } else {
14384         currentMove = target;
14385     }
14386
14387     if (gameMode == EditGame || gameMode == EndOfGame) {
14388         whiteTimeRemaining = timeRemaining[0][currentMove];
14389         blackTimeRemaining = timeRemaining[1][currentMove];
14390     }
14391     DisplayBothClocks();
14392     DisplayMove(currentMove - 1);
14393     DrawPosition(full_redraw, boards[currentMove]);
14394     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14395     // [HGM] PV info: routine tests if comment empty
14396     DisplayComment(currentMove - 1, commentList[currentMove]);
14397 }
14398
14399 void
14400 BackwardEvent()
14401 {
14402     if (gameMode == IcsExamining && !pausing) {
14403         SendToICS(ics_prefix);
14404         SendToICS("backward\n");
14405     } else {
14406         BackwardInner(currentMove - 1);
14407     }
14408 }
14409
14410 void
14411 ToStartEvent()
14412 {
14413     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14414         /* to optimize, we temporarily turn off analysis mode while we undo
14415          * all the moves. Otherwise we get analysis output after each undo.
14416          */
14417         if (first.analysisSupport) {
14418           SendToProgram("exit\nforce\n", &first);
14419           first.analyzing = FALSE;
14420         }
14421     }
14422
14423     if (gameMode == IcsExamining && !pausing) {
14424         SendToICS(ics_prefix);
14425         SendToICS("backward 999999\n");
14426     } else {
14427         BackwardInner(backwardMostMove);
14428     }
14429
14430     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14431         /* we have fed all the moves, so reactivate analysis mode */
14432         SendToProgram("analyze\n", &first);
14433         first.analyzing = TRUE;
14434         /*first.maybeThinking = TRUE;*/
14435         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14436     }
14437 }
14438
14439 void
14440 ToNrEvent(int to)
14441 {
14442   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14443   if (to >= forwardMostMove) to = forwardMostMove;
14444   if (to <= backwardMostMove) to = backwardMostMove;
14445   if (to < currentMove) {
14446     BackwardInner(to);
14447   } else {
14448     ForwardInner(to);
14449   }
14450 }
14451
14452 void
14453 RevertEvent(Boolean annotate)
14454 {
14455     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14456         return;
14457     }
14458     if (gameMode != IcsExamining) {
14459         DisplayError(_("You are not examining a game"), 0);
14460         return;
14461     }
14462     if (pausing) {
14463         DisplayError(_("You can't revert while pausing"), 0);
14464         return;
14465     }
14466     SendToICS(ics_prefix);
14467     SendToICS("revert\n");
14468 }
14469
14470 void
14471 RetractMoveEvent()
14472 {
14473     switch (gameMode) {
14474       case MachinePlaysWhite:
14475       case MachinePlaysBlack:
14476         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14477             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14478             return;
14479         }
14480         if (forwardMostMove < 2) return;
14481         currentMove = forwardMostMove = forwardMostMove - 2;
14482         whiteTimeRemaining = timeRemaining[0][currentMove];
14483         blackTimeRemaining = timeRemaining[1][currentMove];
14484         DisplayBothClocks();
14485         DisplayMove(currentMove - 1);
14486         ClearHighlights();/*!! could figure this out*/
14487         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14488         SendToProgram("remove\n", &first);
14489         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14490         break;
14491
14492       case BeginningOfGame:
14493       default:
14494         break;
14495
14496       case IcsPlayingWhite:
14497       case IcsPlayingBlack:
14498         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14499             SendToICS(ics_prefix);
14500             SendToICS("takeback 2\n");
14501         } else {
14502             SendToICS(ics_prefix);
14503             SendToICS("takeback 1\n");
14504         }
14505         break;
14506     }
14507 }
14508
14509 void
14510 MoveNowEvent()
14511 {
14512     ChessProgramState *cps;
14513
14514     switch (gameMode) {
14515       case MachinePlaysWhite:
14516         if (!WhiteOnMove(forwardMostMove)) {
14517             DisplayError(_("It is your turn"), 0);
14518             return;
14519         }
14520         cps = &first;
14521         break;
14522       case MachinePlaysBlack:
14523         if (WhiteOnMove(forwardMostMove)) {
14524             DisplayError(_("It is your turn"), 0);
14525             return;
14526         }
14527         cps = &first;
14528         break;
14529       case TwoMachinesPlay:
14530         if (WhiteOnMove(forwardMostMove) ==
14531             (first.twoMachinesColor[0] == 'w')) {
14532             cps = &first;
14533         } else {
14534             cps = &second;
14535         }
14536         break;
14537       case BeginningOfGame:
14538       default:
14539         return;
14540     }
14541     SendToProgram("?\n", cps);
14542 }
14543
14544 void
14545 TruncateGameEvent()
14546 {
14547     EditGameEvent();
14548     if (gameMode != EditGame) return;
14549     TruncateGame();
14550 }
14551
14552 void
14553 TruncateGame()
14554 {
14555     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14556     if (forwardMostMove > currentMove) {
14557         if (gameInfo.resultDetails != NULL) {
14558             free(gameInfo.resultDetails);
14559             gameInfo.resultDetails = NULL;
14560             gameInfo.result = GameUnfinished;
14561         }
14562         forwardMostMove = currentMove;
14563         HistorySet(parseList, backwardMostMove, forwardMostMove,
14564                    currentMove-1);
14565     }
14566 }
14567
14568 void
14569 HintEvent()
14570 {
14571     if (appData.noChessProgram) return;
14572     switch (gameMode) {
14573       case MachinePlaysWhite:
14574         if (WhiteOnMove(forwardMostMove)) {
14575             DisplayError(_("Wait until your turn"), 0);
14576             return;
14577         }
14578         break;
14579       case BeginningOfGame:
14580       case MachinePlaysBlack:
14581         if (!WhiteOnMove(forwardMostMove)) {
14582             DisplayError(_("Wait until your turn"), 0);
14583             return;
14584         }
14585         break;
14586       default:
14587         DisplayError(_("No hint available"), 0);
14588         return;
14589     }
14590     SendToProgram("hint\n", &first);
14591     hintRequested = TRUE;
14592 }
14593
14594 void
14595 BookEvent()
14596 {
14597     if (appData.noChessProgram) return;
14598     switch (gameMode) {
14599       case MachinePlaysWhite:
14600         if (WhiteOnMove(forwardMostMove)) {
14601             DisplayError(_("Wait until your turn"), 0);
14602             return;
14603         }
14604         break;
14605       case BeginningOfGame:
14606       case MachinePlaysBlack:
14607         if (!WhiteOnMove(forwardMostMove)) {
14608             DisplayError(_("Wait until your turn"), 0);
14609             return;
14610         }
14611         break;
14612       case EditPosition:
14613         EditPositionDone(TRUE);
14614         break;
14615       case TwoMachinesPlay:
14616         return;
14617       default:
14618         break;
14619     }
14620     SendToProgram("bk\n", &first);
14621     bookOutput[0] = NULLCHAR;
14622     bookRequested = TRUE;
14623 }
14624
14625 void
14626 AboutGameEvent()
14627 {
14628     char *tags = PGNTags(&gameInfo);
14629     TagsPopUp(tags, CmailMsg());
14630     free(tags);
14631 }
14632
14633 /* end button procedures */
14634
14635 void
14636 PrintPosition(fp, move)
14637      FILE *fp;
14638      int move;
14639 {
14640     int i, j;
14641
14642     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14643         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14644             char c = PieceToChar(boards[move][i][j]);
14645             fputc(c == 'x' ? '.' : c, fp);
14646             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14647         }
14648     }
14649     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14650       fprintf(fp, "white to play\n");
14651     else
14652       fprintf(fp, "black to play\n");
14653 }
14654
14655 void
14656 PrintOpponents(fp)
14657      FILE *fp;
14658 {
14659     if (gameInfo.white != NULL) {
14660         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14661     } else {
14662         fprintf(fp, "\n");
14663     }
14664 }
14665
14666 /* Find last component of program's own name, using some heuristics */
14667 void
14668 TidyProgramName(prog, host, buf)
14669      char *prog, *host, buf[MSG_SIZ];
14670 {
14671     char *p, *q;
14672     int local = (strcmp(host, "localhost") == 0);
14673     while (!local && (p = strchr(prog, ';')) != NULL) {
14674         p++;
14675         while (*p == ' ') p++;
14676         prog = p;
14677     }
14678     if (*prog == '"' || *prog == '\'') {
14679         q = strchr(prog + 1, *prog);
14680     } else {
14681         q = strchr(prog, ' ');
14682     }
14683     if (q == NULL) q = prog + strlen(prog);
14684     p = q;
14685     while (p >= prog && *p != '/' && *p != '\\') p--;
14686     p++;
14687     if(p == prog && *p == '"') p++;
14688     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14689     memcpy(buf, p, q - p);
14690     buf[q - p] = NULLCHAR;
14691     if (!local) {
14692         strcat(buf, "@");
14693         strcat(buf, host);
14694     }
14695 }
14696
14697 char *
14698 TimeControlTagValue()
14699 {
14700     char buf[MSG_SIZ];
14701     if (!appData.clockMode) {
14702       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14703     } else if (movesPerSession > 0) {
14704       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14705     } else if (timeIncrement == 0) {
14706       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14707     } else {
14708       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14709     }
14710     return StrSave(buf);
14711 }
14712
14713 void
14714 SetGameInfo()
14715 {
14716     /* This routine is used only for certain modes */
14717     VariantClass v = gameInfo.variant;
14718     ChessMove r = GameUnfinished;
14719     char *p = NULL;
14720
14721     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14722         r = gameInfo.result;
14723         p = gameInfo.resultDetails;
14724         gameInfo.resultDetails = NULL;
14725     }
14726     ClearGameInfo(&gameInfo);
14727     gameInfo.variant = v;
14728
14729     switch (gameMode) {
14730       case MachinePlaysWhite:
14731         gameInfo.event = StrSave( appData.pgnEventHeader );
14732         gameInfo.site = StrSave(HostName());
14733         gameInfo.date = PGNDate();
14734         gameInfo.round = StrSave("-");
14735         gameInfo.white = StrSave(first.tidy);
14736         gameInfo.black = StrSave(UserName());
14737         gameInfo.timeControl = TimeControlTagValue();
14738         break;
14739
14740       case MachinePlaysBlack:
14741         gameInfo.event = StrSave( appData.pgnEventHeader );
14742         gameInfo.site = StrSave(HostName());
14743         gameInfo.date = PGNDate();
14744         gameInfo.round = StrSave("-");
14745         gameInfo.white = StrSave(UserName());
14746         gameInfo.black = StrSave(first.tidy);
14747         gameInfo.timeControl = TimeControlTagValue();
14748         break;
14749
14750       case TwoMachinesPlay:
14751         gameInfo.event = StrSave( appData.pgnEventHeader );
14752         gameInfo.site = StrSave(HostName());
14753         gameInfo.date = PGNDate();
14754         if (roundNr > 0) {
14755             char buf[MSG_SIZ];
14756             snprintf(buf, MSG_SIZ, "%d", roundNr);
14757             gameInfo.round = StrSave(buf);
14758         } else {
14759             gameInfo.round = StrSave("-");
14760         }
14761         if (first.twoMachinesColor[0] == 'w') {
14762             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14763             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14764         } else {
14765             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14766             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14767         }
14768         gameInfo.timeControl = TimeControlTagValue();
14769         break;
14770
14771       case EditGame:
14772         gameInfo.event = StrSave("Edited game");
14773         gameInfo.site = StrSave(HostName());
14774         gameInfo.date = PGNDate();
14775         gameInfo.round = StrSave("-");
14776         gameInfo.white = StrSave("-");
14777         gameInfo.black = StrSave("-");
14778         gameInfo.result = r;
14779         gameInfo.resultDetails = p;
14780         break;
14781
14782       case EditPosition:
14783         gameInfo.event = StrSave("Edited position");
14784         gameInfo.site = StrSave(HostName());
14785         gameInfo.date = PGNDate();
14786         gameInfo.round = StrSave("-");
14787         gameInfo.white = StrSave("-");
14788         gameInfo.black = StrSave("-");
14789         break;
14790
14791       case IcsPlayingWhite:
14792       case IcsPlayingBlack:
14793       case IcsObserving:
14794       case IcsExamining:
14795         break;
14796
14797       case PlayFromGameFile:
14798         gameInfo.event = StrSave("Game from non-PGN file");
14799         gameInfo.site = StrSave(HostName());
14800         gameInfo.date = PGNDate();
14801         gameInfo.round = StrSave("-");
14802         gameInfo.white = StrSave("?");
14803         gameInfo.black = StrSave("?");
14804         break;
14805
14806       default:
14807         break;
14808     }
14809 }
14810
14811 void
14812 ReplaceComment(index, text)
14813      int index;
14814      char *text;
14815 {
14816     int len;
14817     char *p;
14818     float score;
14819
14820     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14821        pvInfoList[index-1].depth == len &&
14822        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14823        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14824     while (*text == '\n') text++;
14825     len = strlen(text);
14826     while (len > 0 && text[len - 1] == '\n') len--;
14827
14828     if (commentList[index] != NULL)
14829       free(commentList[index]);
14830
14831     if (len == 0) {
14832         commentList[index] = NULL;
14833         return;
14834     }
14835   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14836       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14837       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14838     commentList[index] = (char *) malloc(len + 2);
14839     strncpy(commentList[index], text, len);
14840     commentList[index][len] = '\n';
14841     commentList[index][len + 1] = NULLCHAR;
14842   } else {
14843     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14844     char *p;
14845     commentList[index] = (char *) malloc(len + 7);
14846     safeStrCpy(commentList[index], "{\n", 3);
14847     safeStrCpy(commentList[index]+2, text, len+1);
14848     commentList[index][len+2] = NULLCHAR;
14849     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14850     strcat(commentList[index], "\n}\n");
14851   }
14852 }
14853
14854 void
14855 CrushCRs(text)
14856      char *text;
14857 {
14858   char *p = text;
14859   char *q = text;
14860   char ch;
14861
14862   do {
14863     ch = *p++;
14864     if (ch == '\r') continue;
14865     *q++ = ch;
14866   } while (ch != '\0');
14867 }
14868
14869 void
14870 AppendComment(index, text, addBraces)
14871      int index;
14872      char *text;
14873      Boolean addBraces; // [HGM] braces: tells if we should add {}
14874 {
14875     int oldlen, len;
14876     char *old;
14877
14878 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14879     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14880
14881     CrushCRs(text);
14882     while (*text == '\n') text++;
14883     len = strlen(text);
14884     while (len > 0 && text[len - 1] == '\n') len--;
14885
14886     if (len == 0) return;
14887
14888     if (commentList[index] != NULL) {
14889       Boolean addClosingBrace = addBraces;
14890         old = commentList[index];
14891         oldlen = strlen(old);
14892         while(commentList[index][oldlen-1] ==  '\n')
14893           commentList[index][--oldlen] = NULLCHAR;
14894         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14895         safeStrCpy(commentList[index], old, oldlen + len + 6);
14896         free(old);
14897         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14898         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14899           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14900           while (*text == '\n') { text++; len--; }
14901           commentList[index][--oldlen] = NULLCHAR;
14902       }
14903         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14904         else          strcat(commentList[index], "\n");
14905         strcat(commentList[index], text);
14906         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14907         else          strcat(commentList[index], "\n");
14908     } else {
14909         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14910         if(addBraces)
14911           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14912         else commentList[index][0] = NULLCHAR;
14913         strcat(commentList[index], text);
14914         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14915         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14916     }
14917 }
14918
14919 static char * FindStr( char * text, char * sub_text )
14920 {
14921     char * result = strstr( text, sub_text );
14922
14923     if( result != NULL ) {
14924         result += strlen( sub_text );
14925     }
14926
14927     return result;
14928 }
14929
14930 /* [AS] Try to extract PV info from PGN comment */
14931 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14932 char *GetInfoFromComment( int index, char * text )
14933 {
14934     char * sep = text, *p;
14935
14936     if( text != NULL && index > 0 ) {
14937         int score = 0;
14938         int depth = 0;
14939         int time = -1, sec = 0, deci;
14940         char * s_eval = FindStr( text, "[%eval " );
14941         char * s_emt = FindStr( text, "[%emt " );
14942
14943         if( s_eval != NULL || s_emt != NULL ) {
14944             /* New style */
14945             char delim;
14946
14947             if( s_eval != NULL ) {
14948                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14949                     return text;
14950                 }
14951
14952                 if( delim != ']' ) {
14953                     return text;
14954                 }
14955             }
14956
14957             if( s_emt != NULL ) {
14958             }
14959                 return text;
14960         }
14961         else {
14962             /* We expect something like: [+|-]nnn.nn/dd */
14963             int score_lo = 0;
14964
14965             if(*text != '{') return text; // [HGM] braces: must be normal comment
14966
14967             sep = strchr( text, '/' );
14968             if( sep == NULL || sep < (text+4) ) {
14969                 return text;
14970             }
14971
14972             p = text;
14973             if(p[1] == '(') { // comment starts with PV
14974                p = strchr(p, ')'); // locate end of PV
14975                if(p == NULL || sep < p+5) return text;
14976                // at this point we have something like "{(.*) +0.23/6 ..."
14977                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14978                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14979                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14980             }
14981             time = -1; sec = -1; deci = -1;
14982             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14983                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14984                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14985                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14986                 return text;
14987             }
14988
14989             if( score_lo < 0 || score_lo >= 100 ) {
14990                 return text;
14991             }
14992
14993             if(sec >= 0) time = 600*time + 10*sec; else
14994             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14995
14996             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14997
14998             /* [HGM] PV time: now locate end of PV info */
14999             while( *++sep >= '0' && *sep <= '9'); // strip depth
15000             if(time >= 0)
15001             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15002             if(sec >= 0)
15003             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15004             if(deci >= 0)
15005             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15006             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15007         }
15008
15009         if( depth <= 0 ) {
15010             return text;
15011         }
15012
15013         if( time < 0 ) {
15014             time = -1;
15015         }
15016
15017         pvInfoList[index-1].depth = depth;
15018         pvInfoList[index-1].score = score;
15019         pvInfoList[index-1].time  = 10*time; // centi-sec
15020         if(*sep == '}') *sep = 0; else *--sep = '{';
15021         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15022     }
15023     return sep;
15024 }
15025
15026 void
15027 SendToProgram(message, cps)
15028      char *message;
15029      ChessProgramState *cps;
15030 {
15031     int count, outCount, error;
15032     char buf[MSG_SIZ];
15033
15034     if (cps->pr == NoProc) return;
15035     Attention(cps);
15036
15037     if (appData.debugMode) {
15038         TimeMark now;
15039         GetTimeMark(&now);
15040         fprintf(debugFP, "%ld >%-6s: %s",
15041                 SubtractTimeMarks(&now, &programStartTime),
15042                 cps->which, message);
15043     }
15044
15045     count = strlen(message);
15046     outCount = OutputToProcess(cps->pr, message, count, &error);
15047     if (outCount < count && !exiting
15048                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15049       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15050       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15051         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15052             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15053                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15054                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15055                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15056             } else {
15057                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15058                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15059                 gameInfo.result = res;
15060             }
15061             gameInfo.resultDetails = StrSave(buf);
15062         }
15063         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15064         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15065     }
15066 }
15067
15068 void
15069 ReceiveFromProgram(isr, closure, message, count, error)
15070      InputSourceRef isr;
15071      VOIDSTAR closure;
15072      char *message;
15073      int count;
15074      int error;
15075 {
15076     char *end_str;
15077     char buf[MSG_SIZ];
15078     ChessProgramState *cps = (ChessProgramState *)closure;
15079
15080     if (isr != cps->isr) return; /* Killed intentionally */
15081     if (count <= 0) {
15082         if (count == 0) {
15083             RemoveInputSource(cps->isr);
15084             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15085             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15086                     _(cps->which), cps->program);
15087         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15088                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15089                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15090                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15091                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15092                 } else {
15093                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15094                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15095                     gameInfo.result = res;
15096                 }
15097                 gameInfo.resultDetails = StrSave(buf);
15098             }
15099             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15100             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15101         } else {
15102             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15103                     _(cps->which), cps->program);
15104             RemoveInputSource(cps->isr);
15105
15106             /* [AS] Program is misbehaving badly... kill it */
15107             if( count == -2 ) {
15108                 DestroyChildProcess( cps->pr, 9 );
15109                 cps->pr = NoProc;
15110             }
15111
15112             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15113         }
15114         return;
15115     }
15116
15117     if ((end_str = strchr(message, '\r')) != NULL)
15118       *end_str = NULLCHAR;
15119     if ((end_str = strchr(message, '\n')) != NULL)
15120       *end_str = NULLCHAR;
15121
15122     if (appData.debugMode) {
15123         TimeMark now; int print = 1;
15124         char *quote = ""; char c; int i;
15125
15126         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15127                 char start = message[0];
15128                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15129                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15130                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15131                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15132                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15133                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15134                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15135                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15136                    sscanf(message, "hint: %c", &c)!=1 && 
15137                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15138                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15139                     print = (appData.engineComments >= 2);
15140                 }
15141                 message[0] = start; // restore original message
15142         }
15143         if(print) {
15144                 GetTimeMark(&now);
15145                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15146                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15147                         quote,
15148                         message);
15149         }
15150     }
15151
15152     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15153     if (appData.icsEngineAnalyze) {
15154         if (strstr(message, "whisper") != NULL ||
15155              strstr(message, "kibitz") != NULL ||
15156             strstr(message, "tellics") != NULL) return;
15157     }
15158
15159     HandleMachineMove(message, cps);
15160 }
15161
15162
15163 void
15164 SendTimeControl(cps, mps, tc, inc, sd, st)
15165      ChessProgramState *cps;
15166      int mps, inc, sd, st;
15167      long tc;
15168 {
15169     char buf[MSG_SIZ];
15170     int seconds;
15171
15172     if( timeControl_2 > 0 ) {
15173         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15174             tc = timeControl_2;
15175         }
15176     }
15177     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15178     inc /= cps->timeOdds;
15179     st  /= cps->timeOdds;
15180
15181     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15182
15183     if (st > 0) {
15184       /* Set exact time per move, normally using st command */
15185       if (cps->stKludge) {
15186         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15187         seconds = st % 60;
15188         if (seconds == 0) {
15189           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15190         } else {
15191           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15192         }
15193       } else {
15194         snprintf(buf, MSG_SIZ, "st %d\n", st);
15195       }
15196     } else {
15197       /* Set conventional or incremental time control, using level command */
15198       if (seconds == 0) {
15199         /* Note old gnuchess bug -- minutes:seconds used to not work.
15200            Fixed in later versions, but still avoid :seconds
15201            when seconds is 0. */
15202         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15203       } else {
15204         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15205                  seconds, inc/1000.);
15206       }
15207     }
15208     SendToProgram(buf, cps);
15209
15210     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15211     /* Orthogonally, limit search to given depth */
15212     if (sd > 0) {
15213       if (cps->sdKludge) {
15214         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15215       } else {
15216         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15217       }
15218       SendToProgram(buf, cps);
15219     }
15220
15221     if(cps->nps >= 0) { /* [HGM] nps */
15222         if(cps->supportsNPS == FALSE)
15223           cps->nps = -1; // don't use if engine explicitly says not supported!
15224         else {
15225           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15226           SendToProgram(buf, cps);
15227         }
15228     }
15229 }
15230
15231 ChessProgramState *WhitePlayer()
15232 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15233 {
15234     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15235        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15236         return &second;
15237     return &first;
15238 }
15239
15240 void
15241 SendTimeRemaining(cps, machineWhite)
15242      ChessProgramState *cps;
15243      int /*boolean*/ machineWhite;
15244 {
15245     char message[MSG_SIZ];
15246     long time, otime;
15247
15248     /* Note: this routine must be called when the clocks are stopped
15249        or when they have *just* been set or switched; otherwise
15250        it will be off by the time since the current tick started.
15251     */
15252     if (machineWhite) {
15253         time = whiteTimeRemaining / 10;
15254         otime = blackTimeRemaining / 10;
15255     } else {
15256         time = blackTimeRemaining / 10;
15257         otime = whiteTimeRemaining / 10;
15258     }
15259     /* [HGM] translate opponent's time by time-odds factor */
15260     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15261     if (appData.debugMode) {
15262         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15263     }
15264
15265     if (time <= 0) time = 1;
15266     if (otime <= 0) otime = 1;
15267
15268     snprintf(message, MSG_SIZ, "time %ld\n", time);
15269     SendToProgram(message, cps);
15270
15271     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15272     SendToProgram(message, cps);
15273 }
15274
15275 int
15276 BoolFeature(p, name, loc, cps)
15277      char **p;
15278      char *name;
15279      int *loc;
15280      ChessProgramState *cps;
15281 {
15282   char buf[MSG_SIZ];
15283   int len = strlen(name);
15284   int val;
15285
15286   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15287     (*p) += len + 1;
15288     sscanf(*p, "%d", &val);
15289     *loc = (val != 0);
15290     while (**p && **p != ' ')
15291       (*p)++;
15292     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15293     SendToProgram(buf, cps);
15294     return TRUE;
15295   }
15296   return FALSE;
15297 }
15298
15299 int
15300 IntFeature(p, name, loc, cps)
15301      char **p;
15302      char *name;
15303      int *loc;
15304      ChessProgramState *cps;
15305 {
15306   char buf[MSG_SIZ];
15307   int len = strlen(name);
15308   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15309     (*p) += len + 1;
15310     sscanf(*p, "%d", loc);
15311     while (**p && **p != ' ') (*p)++;
15312     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15313     SendToProgram(buf, cps);
15314     return TRUE;
15315   }
15316   return FALSE;
15317 }
15318
15319 int
15320 StringFeature(p, name, loc, cps)
15321      char **p;
15322      char *name;
15323      char loc[];
15324      ChessProgramState *cps;
15325 {
15326   char buf[MSG_SIZ];
15327   int len = strlen(name);
15328   if (strncmp((*p), name, len) == 0
15329       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15330     (*p) += len + 2;
15331     sscanf(*p, "%[^\"]", loc);
15332     while (**p && **p != '\"') (*p)++;
15333     if (**p == '\"') (*p)++;
15334     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15335     SendToProgram(buf, cps);
15336     return TRUE;
15337   }
15338   return FALSE;
15339 }
15340
15341 int
15342 ParseOption(Option *opt, ChessProgramState *cps)
15343 // [HGM] options: process the string that defines an engine option, and determine
15344 // name, type, default value, and allowed value range
15345 {
15346         char *p, *q, buf[MSG_SIZ];
15347         int n, min = (-1)<<31, max = 1<<31, def;
15348
15349         if(p = strstr(opt->name, " -spin ")) {
15350             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15351             if(max < min) max = min; // enforce consistency
15352             if(def < min) def = min;
15353             if(def > max) def = max;
15354             opt->value = def;
15355             opt->min = min;
15356             opt->max = max;
15357             opt->type = Spin;
15358         } else if((p = strstr(opt->name, " -slider "))) {
15359             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15360             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15361             if(max < min) max = min; // enforce consistency
15362             if(def < min) def = min;
15363             if(def > max) def = max;
15364             opt->value = def;
15365             opt->min = min;
15366             opt->max = max;
15367             opt->type = Spin; // Slider;
15368         } else if((p = strstr(opt->name, " -string "))) {
15369             opt->textValue = p+9;
15370             opt->type = TextBox;
15371         } else if((p = strstr(opt->name, " -file "))) {
15372             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15373             opt->textValue = p+7;
15374             opt->type = FileName; // FileName;
15375         } else if((p = strstr(opt->name, " -path "))) {
15376             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15377             opt->textValue = p+7;
15378             opt->type = PathName; // PathName;
15379         } else if(p = strstr(opt->name, " -check ")) {
15380             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15381             opt->value = (def != 0);
15382             opt->type = CheckBox;
15383         } else if(p = strstr(opt->name, " -combo ")) {
15384             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15385             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15386             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15387             opt->value = n = 0;
15388             while(q = StrStr(q, " /// ")) {
15389                 n++; *q = 0;    // count choices, and null-terminate each of them
15390                 q += 5;
15391                 if(*q == '*') { // remember default, which is marked with * prefix
15392                     q++;
15393                     opt->value = n;
15394                 }
15395                 cps->comboList[cps->comboCnt++] = q;
15396             }
15397             cps->comboList[cps->comboCnt++] = NULL;
15398             opt->max = n + 1;
15399             opt->type = ComboBox;
15400         } else if(p = strstr(opt->name, " -button")) {
15401             opt->type = Button;
15402         } else if(p = strstr(opt->name, " -save")) {
15403             opt->type = SaveButton;
15404         } else return FALSE;
15405         *p = 0; // terminate option name
15406         // now look if the command-line options define a setting for this engine option.
15407         if(cps->optionSettings && cps->optionSettings[0])
15408             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15409         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15410           snprintf(buf, MSG_SIZ, "option %s", p);
15411                 if(p = strstr(buf, ",")) *p = 0;
15412                 if(q = strchr(buf, '=')) switch(opt->type) {
15413                     case ComboBox:
15414                         for(n=0; n<opt->max; n++)
15415                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15416                         break;
15417                     case TextBox:
15418                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15419                         break;
15420                     case Spin:
15421                     case CheckBox:
15422                         opt->value = atoi(q+1);
15423                     default:
15424                         break;
15425                 }
15426                 strcat(buf, "\n");
15427                 SendToProgram(buf, cps);
15428         }
15429         return TRUE;
15430 }
15431
15432 void
15433 FeatureDone(cps, val)
15434      ChessProgramState* cps;
15435      int val;
15436 {
15437   DelayedEventCallback cb = GetDelayedEvent();
15438   if ((cb == InitBackEnd3 && cps == &first) ||
15439       (cb == SettingsMenuIfReady && cps == &second) ||
15440       (cb == LoadEngine) ||
15441       (cb == TwoMachinesEventIfReady)) {
15442     CancelDelayedEvent();
15443     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15444   }
15445   cps->initDone = val;
15446 }
15447
15448 /* Parse feature command from engine */
15449 void
15450 ParseFeatures(args, cps)
15451      char* args;
15452      ChessProgramState *cps;
15453 {
15454   char *p = args;
15455   char *q;
15456   int val;
15457   char buf[MSG_SIZ];
15458
15459   for (;;) {
15460     while (*p == ' ') p++;
15461     if (*p == NULLCHAR) return;
15462
15463     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15464     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15465     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15466     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15467     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15468     if (BoolFeature(&p, "reuse", &val, cps)) {
15469       /* Engine can disable reuse, but can't enable it if user said no */
15470       if (!val) cps->reuse = FALSE;
15471       continue;
15472     }
15473     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15474     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15475       if (gameMode == TwoMachinesPlay) {
15476         DisplayTwoMachinesTitle();
15477       } else {
15478         DisplayTitle("");
15479       }
15480       continue;
15481     }
15482     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15483     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15484     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15485     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15486     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15487     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15488     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15489     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15490     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15491     if (IntFeature(&p, "done", &val, cps)) {
15492       FeatureDone(cps, val);
15493       continue;
15494     }
15495     /* Added by Tord: */
15496     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15497     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15498     /* End of additions by Tord */
15499
15500     /* [HGM] added features: */
15501     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15502     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15503     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15504     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15505     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15506     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15507     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15508         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15509           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15510             SendToProgram(buf, cps);
15511             continue;
15512         }
15513         if(cps->nrOptions >= MAX_OPTIONS) {
15514             cps->nrOptions--;
15515             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15516             DisplayError(buf, 0);
15517         }
15518         continue;
15519     }
15520     /* End of additions by HGM */
15521
15522     /* unknown feature: complain and skip */
15523     q = p;
15524     while (*q && *q != '=') q++;
15525     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15526     SendToProgram(buf, cps);
15527     p = q;
15528     if (*p == '=') {
15529       p++;
15530       if (*p == '\"') {
15531         p++;
15532         while (*p && *p != '\"') p++;
15533         if (*p == '\"') p++;
15534       } else {
15535         while (*p && *p != ' ') p++;
15536       }
15537     }
15538   }
15539
15540 }
15541
15542 void
15543 PeriodicUpdatesEvent(newState)
15544      int newState;
15545 {
15546     if (newState == appData.periodicUpdates)
15547       return;
15548
15549     appData.periodicUpdates=newState;
15550
15551     /* Display type changes, so update it now */
15552 //    DisplayAnalysis();
15553
15554     /* Get the ball rolling again... */
15555     if (newState) {
15556         AnalysisPeriodicEvent(1);
15557         StartAnalysisClock();
15558     }
15559 }
15560
15561 void
15562 PonderNextMoveEvent(newState)
15563      int newState;
15564 {
15565     if (newState == appData.ponderNextMove) return;
15566     if (gameMode == EditPosition) EditPositionDone(TRUE);
15567     if (newState) {
15568         SendToProgram("hard\n", &first);
15569         if (gameMode == TwoMachinesPlay) {
15570             SendToProgram("hard\n", &second);
15571         }
15572     } else {
15573         SendToProgram("easy\n", &first);
15574         thinkOutput[0] = NULLCHAR;
15575         if (gameMode == TwoMachinesPlay) {
15576             SendToProgram("easy\n", &second);
15577         }
15578     }
15579     appData.ponderNextMove = newState;
15580 }
15581
15582 void
15583 NewSettingEvent(option, feature, command, value)
15584      char *command;
15585      int option, value, *feature;
15586 {
15587     char buf[MSG_SIZ];
15588
15589     if (gameMode == EditPosition) EditPositionDone(TRUE);
15590     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15591     if(feature == NULL || *feature) SendToProgram(buf, &first);
15592     if (gameMode == TwoMachinesPlay) {
15593         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15594     }
15595 }
15596
15597 void
15598 ShowThinkingEvent()
15599 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15600 {
15601     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15602     int newState = appData.showThinking
15603         // [HGM] thinking: other features now need thinking output as well
15604         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15605
15606     if (oldState == newState) return;
15607     oldState = newState;
15608     if (gameMode == EditPosition) EditPositionDone(TRUE);
15609     if (oldState) {
15610         SendToProgram("post\n", &first);
15611         if (gameMode == TwoMachinesPlay) {
15612             SendToProgram("post\n", &second);
15613         }
15614     } else {
15615         SendToProgram("nopost\n", &first);
15616         thinkOutput[0] = NULLCHAR;
15617         if (gameMode == TwoMachinesPlay) {
15618             SendToProgram("nopost\n", &second);
15619         }
15620     }
15621 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15622 }
15623
15624 void
15625 AskQuestionEvent(title, question, replyPrefix, which)
15626      char *title; char *question; char *replyPrefix; char *which;
15627 {
15628   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15629   if (pr == NoProc) return;
15630   AskQuestion(title, question, replyPrefix, pr);
15631 }
15632
15633 void
15634 TypeInEvent(char firstChar)
15635 {
15636     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15637         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15638         gameMode == AnalyzeMode || gameMode == EditGame || 
15639         gameMode == EditPosition || gameMode == IcsExamining ||
15640         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15641         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15642                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15643                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15644         gameMode == Training) PopUpMoveDialog(firstChar);
15645 }
15646
15647 void
15648 TypeInDoneEvent(char *move)
15649 {
15650         Board board;
15651         int n, fromX, fromY, toX, toY;
15652         char promoChar;
15653         ChessMove moveType;
15654
15655         // [HGM] FENedit
15656         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15657                 EditPositionPasteFEN(move);
15658                 return;
15659         }
15660         // [HGM] movenum: allow move number to be typed in any mode
15661         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15662           ToNrEvent(2*n-1);
15663           return;
15664         }
15665
15666       if (gameMode != EditGame && currentMove != forwardMostMove && 
15667         gameMode != Training) {
15668         DisplayMoveError(_("Displayed move is not current"));
15669       } else {
15670         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15671           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15672         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15673         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15674           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15675           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15676         } else {
15677           DisplayMoveError(_("Could not parse move"));
15678         }
15679       }
15680 }
15681
15682 void
15683 DisplayMove(moveNumber)
15684      int moveNumber;
15685 {
15686     char message[MSG_SIZ];
15687     char res[MSG_SIZ];
15688     char cpThinkOutput[MSG_SIZ];
15689
15690     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15691
15692     if (moveNumber == forwardMostMove - 1 ||
15693         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15694
15695         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15696
15697         if (strchr(cpThinkOutput, '\n')) {
15698             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15699         }
15700     } else {
15701         *cpThinkOutput = NULLCHAR;
15702     }
15703
15704     /* [AS] Hide thinking from human user */
15705     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15706         *cpThinkOutput = NULLCHAR;
15707         if( thinkOutput[0] != NULLCHAR ) {
15708             int i;
15709
15710             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15711                 cpThinkOutput[i] = '.';
15712             }
15713             cpThinkOutput[i] = NULLCHAR;
15714             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15715         }
15716     }
15717
15718     if (moveNumber == forwardMostMove - 1 &&
15719         gameInfo.resultDetails != NULL) {
15720         if (gameInfo.resultDetails[0] == NULLCHAR) {
15721           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15722         } else {
15723           snprintf(res, MSG_SIZ, " {%s} %s",
15724                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15725         }
15726     } else {
15727         res[0] = NULLCHAR;
15728     }
15729
15730     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15731         DisplayMessage(res, cpThinkOutput);
15732     } else {
15733       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15734                 WhiteOnMove(moveNumber) ? " " : ".. ",
15735                 parseList[moveNumber], res);
15736         DisplayMessage(message, cpThinkOutput);
15737     }
15738 }
15739
15740 void
15741 DisplayComment(moveNumber, text)
15742      int moveNumber;
15743      char *text;
15744 {
15745     char title[MSG_SIZ];
15746
15747     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15748       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15749     } else {
15750       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15751               WhiteOnMove(moveNumber) ? " " : ".. ",
15752               parseList[moveNumber]);
15753     }
15754     if (text != NULL && (appData.autoDisplayComment || commentUp))
15755         CommentPopUp(title, text);
15756 }
15757
15758 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15759  * might be busy thinking or pondering.  It can be omitted if your
15760  * gnuchess is configured to stop thinking immediately on any user
15761  * input.  However, that gnuchess feature depends on the FIONREAD
15762  * ioctl, which does not work properly on some flavors of Unix.
15763  */
15764 void
15765 Attention(cps)
15766      ChessProgramState *cps;
15767 {
15768 #if ATTENTION
15769     if (!cps->useSigint) return;
15770     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15771     switch (gameMode) {
15772       case MachinePlaysWhite:
15773       case MachinePlaysBlack:
15774       case TwoMachinesPlay:
15775       case IcsPlayingWhite:
15776       case IcsPlayingBlack:
15777       case AnalyzeMode:
15778       case AnalyzeFile:
15779         /* Skip if we know it isn't thinking */
15780         if (!cps->maybeThinking) return;
15781         if (appData.debugMode)
15782           fprintf(debugFP, "Interrupting %s\n", cps->which);
15783         InterruptChildProcess(cps->pr);
15784         cps->maybeThinking = FALSE;
15785         break;
15786       default:
15787         break;
15788     }
15789 #endif /*ATTENTION*/
15790 }
15791
15792 int
15793 CheckFlags()
15794 {
15795     if (whiteTimeRemaining <= 0) {
15796         if (!whiteFlag) {
15797             whiteFlag = TRUE;
15798             if (appData.icsActive) {
15799                 if (appData.autoCallFlag &&
15800                     gameMode == IcsPlayingBlack && !blackFlag) {
15801                   SendToICS(ics_prefix);
15802                   SendToICS("flag\n");
15803                 }
15804             } else {
15805                 if (blackFlag) {
15806                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15807                 } else {
15808                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15809                     if (appData.autoCallFlag) {
15810                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15811                         return TRUE;
15812                     }
15813                 }
15814             }
15815         }
15816     }
15817     if (blackTimeRemaining <= 0) {
15818         if (!blackFlag) {
15819             blackFlag = TRUE;
15820             if (appData.icsActive) {
15821                 if (appData.autoCallFlag &&
15822                     gameMode == IcsPlayingWhite && !whiteFlag) {
15823                   SendToICS(ics_prefix);
15824                   SendToICS("flag\n");
15825                 }
15826             } else {
15827                 if (whiteFlag) {
15828                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15829                 } else {
15830                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15831                     if (appData.autoCallFlag) {
15832                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15833                         return TRUE;
15834                     }
15835                 }
15836             }
15837         }
15838     }
15839     return FALSE;
15840 }
15841
15842 void
15843 CheckTimeControl()
15844 {
15845     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15846         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15847
15848     /*
15849      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15850      */
15851     if ( !WhiteOnMove(forwardMostMove) ) {
15852         /* White made time control */
15853         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15854         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15855         /* [HGM] time odds: correct new time quota for time odds! */
15856                                             / WhitePlayer()->timeOdds;
15857         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15858     } else {
15859         lastBlack -= blackTimeRemaining;
15860         /* Black made time control */
15861         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15862                                             / WhitePlayer()->other->timeOdds;
15863         lastWhite = whiteTimeRemaining;
15864     }
15865 }
15866
15867 void
15868 DisplayBothClocks()
15869 {
15870     int wom = gameMode == EditPosition ?
15871       !blackPlaysFirst : WhiteOnMove(currentMove);
15872     DisplayWhiteClock(whiteTimeRemaining, wom);
15873     DisplayBlackClock(blackTimeRemaining, !wom);
15874 }
15875
15876
15877 /* Timekeeping seems to be a portability nightmare.  I think everyone
15878    has ftime(), but I'm really not sure, so I'm including some ifdefs
15879    to use other calls if you don't.  Clocks will be less accurate if
15880    you have neither ftime nor gettimeofday.
15881 */
15882
15883 /* VS 2008 requires the #include outside of the function */
15884 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15885 #include <sys/timeb.h>
15886 #endif
15887
15888 /* Get the current time as a TimeMark */
15889 void
15890 GetTimeMark(tm)
15891      TimeMark *tm;
15892 {
15893 #if HAVE_GETTIMEOFDAY
15894
15895     struct timeval timeVal;
15896     struct timezone timeZone;
15897
15898     gettimeofday(&timeVal, &timeZone);
15899     tm->sec = (long) timeVal.tv_sec;
15900     tm->ms = (int) (timeVal.tv_usec / 1000L);
15901
15902 #else /*!HAVE_GETTIMEOFDAY*/
15903 #if HAVE_FTIME
15904
15905 // include <sys/timeb.h> / moved to just above start of function
15906     struct timeb timeB;
15907
15908     ftime(&timeB);
15909     tm->sec = (long) timeB.time;
15910     tm->ms = (int) timeB.millitm;
15911
15912 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15913     tm->sec = (long) time(NULL);
15914     tm->ms = 0;
15915 #endif
15916 #endif
15917 }
15918
15919 /* Return the difference in milliseconds between two
15920    time marks.  We assume the difference will fit in a long!
15921 */
15922 long
15923 SubtractTimeMarks(tm2, tm1)
15924      TimeMark *tm2, *tm1;
15925 {
15926     return 1000L*(tm2->sec - tm1->sec) +
15927            (long) (tm2->ms - tm1->ms);
15928 }
15929
15930
15931 /*
15932  * Code to manage the game clocks.
15933  *
15934  * In tournament play, black starts the clock and then white makes a move.
15935  * We give the human user a slight advantage if he is playing white---the
15936  * clocks don't run until he makes his first move, so it takes zero time.
15937  * Also, we don't account for network lag, so we could get out of sync
15938  * with GNU Chess's clock -- but then, referees are always right.
15939  */
15940
15941 static TimeMark tickStartTM;
15942 static long intendedTickLength;
15943
15944 long
15945 NextTickLength(timeRemaining)
15946      long timeRemaining;
15947 {
15948     long nominalTickLength, nextTickLength;
15949
15950     if (timeRemaining > 0L && timeRemaining <= 10000L)
15951       nominalTickLength = 100L;
15952     else
15953       nominalTickLength = 1000L;
15954     nextTickLength = timeRemaining % nominalTickLength;
15955     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15956
15957     return nextTickLength;
15958 }
15959
15960 /* Adjust clock one minute up or down */
15961 void
15962 AdjustClock(Boolean which, int dir)
15963 {
15964     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15965     if(which) blackTimeRemaining += 60000*dir;
15966     else      whiteTimeRemaining += 60000*dir;
15967     DisplayBothClocks();
15968     adjustedClock = TRUE;
15969 }
15970
15971 /* Stop clocks and reset to a fresh time control */
15972 void
15973 ResetClocks()
15974 {
15975     (void) StopClockTimer();
15976     if (appData.icsActive) {
15977         whiteTimeRemaining = blackTimeRemaining = 0;
15978     } else if (searchTime) {
15979         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15980         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15981     } else { /* [HGM] correct new time quote for time odds */
15982         whiteTC = blackTC = fullTimeControlString;
15983         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15984         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15985     }
15986     if (whiteFlag || blackFlag) {
15987         DisplayTitle("");
15988         whiteFlag = blackFlag = FALSE;
15989     }
15990     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15991     DisplayBothClocks();
15992     adjustedClock = FALSE;
15993 }
15994
15995 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15996
15997 /* Decrement running clock by amount of time that has passed */
15998 void
15999 DecrementClocks()
16000 {
16001     long timeRemaining;
16002     long lastTickLength, fudge;
16003     TimeMark now;
16004
16005     if (!appData.clockMode) return;
16006     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16007
16008     GetTimeMark(&now);
16009
16010     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16011
16012     /* Fudge if we woke up a little too soon */
16013     fudge = intendedTickLength - lastTickLength;
16014     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16015
16016     if (WhiteOnMove(forwardMostMove)) {
16017         if(whiteNPS >= 0) lastTickLength = 0;
16018         timeRemaining = whiteTimeRemaining -= lastTickLength;
16019         if(timeRemaining < 0 && !appData.icsActive) {
16020             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16021             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16022                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16023                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16024             }
16025         }
16026         DisplayWhiteClock(whiteTimeRemaining - fudge,
16027                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16028     } else {
16029         if(blackNPS >= 0) lastTickLength = 0;
16030         timeRemaining = blackTimeRemaining -= lastTickLength;
16031         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16032             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16033             if(suddenDeath) {
16034                 blackStartMove = forwardMostMove;
16035                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16036             }
16037         }
16038         DisplayBlackClock(blackTimeRemaining - fudge,
16039                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16040     }
16041     if (CheckFlags()) return;
16042
16043     tickStartTM = now;
16044     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16045     StartClockTimer(intendedTickLength);
16046
16047     /* if the time remaining has fallen below the alarm threshold, sound the
16048      * alarm. if the alarm has sounded and (due to a takeback or time control
16049      * with increment) the time remaining has increased to a level above the
16050      * threshold, reset the alarm so it can sound again.
16051      */
16052
16053     if (appData.icsActive && appData.icsAlarm) {
16054
16055         /* make sure we are dealing with the user's clock */
16056         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16057                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16058            )) return;
16059
16060         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16061             alarmSounded = FALSE;
16062         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16063             PlayAlarmSound();
16064             alarmSounded = TRUE;
16065         }
16066     }
16067 }
16068
16069
16070 /* A player has just moved, so stop the previously running
16071    clock and (if in clock mode) start the other one.
16072    We redisplay both clocks in case we're in ICS mode, because
16073    ICS gives us an update to both clocks after every move.
16074    Note that this routine is called *after* forwardMostMove
16075    is updated, so the last fractional tick must be subtracted
16076    from the color that is *not* on move now.
16077 */
16078 void
16079 SwitchClocks(int newMoveNr)
16080 {
16081     long lastTickLength;
16082     TimeMark now;
16083     int flagged = FALSE;
16084
16085     GetTimeMark(&now);
16086
16087     if (StopClockTimer() && appData.clockMode) {
16088         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16089         if (!WhiteOnMove(forwardMostMove)) {
16090             if(blackNPS >= 0) lastTickLength = 0;
16091             blackTimeRemaining -= lastTickLength;
16092            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16093 //         if(pvInfoList[forwardMostMove].time == -1)
16094                  pvInfoList[forwardMostMove].time =               // use GUI time
16095                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16096         } else {
16097            if(whiteNPS >= 0) lastTickLength = 0;
16098            whiteTimeRemaining -= lastTickLength;
16099            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16100 //         if(pvInfoList[forwardMostMove].time == -1)
16101                  pvInfoList[forwardMostMove].time =
16102                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16103         }
16104         flagged = CheckFlags();
16105     }
16106     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16107     CheckTimeControl();
16108
16109     if (flagged || !appData.clockMode) return;
16110
16111     switch (gameMode) {
16112       case MachinePlaysBlack:
16113       case MachinePlaysWhite:
16114       case BeginningOfGame:
16115         if (pausing) return;
16116         break;
16117
16118       case EditGame:
16119       case PlayFromGameFile:
16120       case IcsExamining:
16121         return;
16122
16123       default:
16124         break;
16125     }
16126
16127     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16128         if(WhiteOnMove(forwardMostMove))
16129              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16130         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16131     }
16132
16133     tickStartTM = now;
16134     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16135       whiteTimeRemaining : blackTimeRemaining);
16136     StartClockTimer(intendedTickLength);
16137 }
16138
16139
16140 /* Stop both clocks */
16141 void
16142 StopClocks()
16143 {
16144     long lastTickLength;
16145     TimeMark now;
16146
16147     if (!StopClockTimer()) return;
16148     if (!appData.clockMode) return;
16149
16150     GetTimeMark(&now);
16151
16152     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16153     if (WhiteOnMove(forwardMostMove)) {
16154         if(whiteNPS >= 0) lastTickLength = 0;
16155         whiteTimeRemaining -= lastTickLength;
16156         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16157     } else {
16158         if(blackNPS >= 0) lastTickLength = 0;
16159         blackTimeRemaining -= lastTickLength;
16160         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16161     }
16162     CheckFlags();
16163 }
16164
16165 /* Start clock of player on move.  Time may have been reset, so
16166    if clock is already running, stop and restart it. */
16167 void
16168 StartClocks()
16169 {
16170     (void) StopClockTimer(); /* in case it was running already */
16171     DisplayBothClocks();
16172     if (CheckFlags()) return;
16173
16174     if (!appData.clockMode) return;
16175     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16176
16177     GetTimeMark(&tickStartTM);
16178     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16179       whiteTimeRemaining : blackTimeRemaining);
16180
16181    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16182     whiteNPS = blackNPS = -1;
16183     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16184        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16185         whiteNPS = first.nps;
16186     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16187        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16188         blackNPS = first.nps;
16189     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16190         whiteNPS = second.nps;
16191     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16192         blackNPS = second.nps;
16193     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16194
16195     StartClockTimer(intendedTickLength);
16196 }
16197
16198 char *
16199 TimeString(ms)
16200      long ms;
16201 {
16202     long second, minute, hour, day;
16203     char *sign = "";
16204     static char buf[32];
16205
16206     if (ms > 0 && ms <= 9900) {
16207       /* convert milliseconds to tenths, rounding up */
16208       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16209
16210       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16211       return buf;
16212     }
16213
16214     /* convert milliseconds to seconds, rounding up */
16215     /* use floating point to avoid strangeness of integer division
16216        with negative dividends on many machines */
16217     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16218
16219     if (second < 0) {
16220         sign = "-";
16221         second = -second;
16222     }
16223
16224     day = second / (60 * 60 * 24);
16225     second = second % (60 * 60 * 24);
16226     hour = second / (60 * 60);
16227     second = second % (60 * 60);
16228     minute = second / 60;
16229     second = second % 60;
16230
16231     if (day > 0)
16232       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16233               sign, day, hour, minute, second);
16234     else if (hour > 0)
16235       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16236     else
16237       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16238
16239     return buf;
16240 }
16241
16242
16243 /*
16244  * This is necessary because some C libraries aren't ANSI C compliant yet.
16245  */
16246 char *
16247 StrStr(string, match)
16248      char *string, *match;
16249 {
16250     int i, length;
16251
16252     length = strlen(match);
16253
16254     for (i = strlen(string) - length; i >= 0; i--, string++)
16255       if (!strncmp(match, string, length))
16256         return string;
16257
16258     return NULL;
16259 }
16260
16261 char *
16262 StrCaseStr(string, match)
16263      char *string, *match;
16264 {
16265     int i, j, length;
16266
16267     length = strlen(match);
16268
16269     for (i = strlen(string) - length; i >= 0; i--, string++) {
16270         for (j = 0; j < length; j++) {
16271             if (ToLower(match[j]) != ToLower(string[j]))
16272               break;
16273         }
16274         if (j == length) return string;
16275     }
16276
16277     return NULL;
16278 }
16279
16280 #ifndef _amigados
16281 int
16282 StrCaseCmp(s1, s2)
16283      char *s1, *s2;
16284 {
16285     char c1, c2;
16286
16287     for (;;) {
16288         c1 = ToLower(*s1++);
16289         c2 = ToLower(*s2++);
16290         if (c1 > c2) return 1;
16291         if (c1 < c2) return -1;
16292         if (c1 == NULLCHAR) return 0;
16293     }
16294 }
16295
16296
16297 int
16298 ToLower(c)
16299      int c;
16300 {
16301     return isupper(c) ? tolower(c) : c;
16302 }
16303
16304
16305 int
16306 ToUpper(c)
16307      int c;
16308 {
16309     return islower(c) ? toupper(c) : c;
16310 }
16311 #endif /* !_amigados    */
16312
16313 char *
16314 StrSave(s)
16315      char *s;
16316 {
16317   char *ret;
16318
16319   if ((ret = (char *) malloc(strlen(s) + 1)))
16320     {
16321       safeStrCpy(ret, s, strlen(s)+1);
16322     }
16323   return ret;
16324 }
16325
16326 char *
16327 StrSavePtr(s, savePtr)
16328      char *s, **savePtr;
16329 {
16330     if (*savePtr) {
16331         free(*savePtr);
16332     }
16333     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16334       safeStrCpy(*savePtr, s, strlen(s)+1);
16335     }
16336     return(*savePtr);
16337 }
16338
16339 char *
16340 PGNDate()
16341 {
16342     time_t clock;
16343     struct tm *tm;
16344     char buf[MSG_SIZ];
16345
16346     clock = time((time_t *)NULL);
16347     tm = localtime(&clock);
16348     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16349             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16350     return StrSave(buf);
16351 }
16352
16353
16354 char *
16355 PositionToFEN(move, overrideCastling)
16356      int move;
16357      char *overrideCastling;
16358 {
16359     int i, j, fromX, fromY, toX, toY;
16360     int whiteToPlay;
16361     char buf[MSG_SIZ];
16362     char *p, *q;
16363     int emptycount;
16364     ChessSquare piece;
16365
16366     whiteToPlay = (gameMode == EditPosition) ?
16367       !blackPlaysFirst : (move % 2 == 0);
16368     p = buf;
16369
16370     /* Piece placement data */
16371     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16372         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16373         emptycount = 0;
16374         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16375             if (boards[move][i][j] == EmptySquare) {
16376                 emptycount++;
16377             } else { ChessSquare piece = boards[move][i][j];
16378                 if (emptycount > 0) {
16379                     if(emptycount<10) /* [HGM] can be >= 10 */
16380                         *p++ = '0' + emptycount;
16381                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16382                     emptycount = 0;
16383                 }
16384                 if(PieceToChar(piece) == '+') {
16385                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16386                     *p++ = '+';
16387                     piece = (ChessSquare)(DEMOTED piece);
16388                 }
16389                 *p++ = PieceToChar(piece);
16390                 if(p[-1] == '~') {
16391                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16392                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16393                     *p++ = '~';
16394                 }
16395             }
16396         }
16397         if (emptycount > 0) {
16398             if(emptycount<10) /* [HGM] can be >= 10 */
16399                 *p++ = '0' + emptycount;
16400             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16401             emptycount = 0;
16402         }
16403         *p++ = '/';
16404     }
16405     *(p - 1) = ' ';
16406
16407     /* [HGM] print Crazyhouse or Shogi holdings */
16408     if( gameInfo.holdingsWidth ) {
16409         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16410         q = p;
16411         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16412             piece = boards[move][i][BOARD_WIDTH-1];
16413             if( piece != EmptySquare )
16414               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16415                   *p++ = PieceToChar(piece);
16416         }
16417         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16418             piece = boards[move][BOARD_HEIGHT-i-1][0];
16419             if( piece != EmptySquare )
16420               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16421                   *p++ = PieceToChar(piece);
16422         }
16423
16424         if( q == p ) *p++ = '-';
16425         *p++ = ']';
16426         *p++ = ' ';
16427     }
16428
16429     /* Active color */
16430     *p++ = whiteToPlay ? 'w' : 'b';
16431     *p++ = ' ';
16432
16433   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16434     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16435   } else {
16436   if(nrCastlingRights) {
16437      q = p;
16438      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16439        /* [HGM] write directly from rights */
16440            if(boards[move][CASTLING][2] != NoRights &&
16441               boards[move][CASTLING][0] != NoRights   )
16442                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16443            if(boards[move][CASTLING][2] != NoRights &&
16444               boards[move][CASTLING][1] != NoRights   )
16445                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16446            if(boards[move][CASTLING][5] != NoRights &&
16447               boards[move][CASTLING][3] != NoRights   )
16448                 *p++ = boards[move][CASTLING][3] + AAA;
16449            if(boards[move][CASTLING][5] != NoRights &&
16450               boards[move][CASTLING][4] != NoRights   )
16451                 *p++ = boards[move][CASTLING][4] + AAA;
16452      } else {
16453
16454         /* [HGM] write true castling rights */
16455         if( nrCastlingRights == 6 ) {
16456             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16457                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16458             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16459                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16460             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16461                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16462             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16463                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16464         }
16465      }
16466      if (q == p) *p++ = '-'; /* No castling rights */
16467      *p++ = ' ';
16468   }
16469
16470   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16471      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16472     /* En passant target square */
16473     if (move > backwardMostMove) {
16474         fromX = moveList[move - 1][0] - AAA;
16475         fromY = moveList[move - 1][1] - ONE;
16476         toX = moveList[move - 1][2] - AAA;
16477         toY = moveList[move - 1][3] - ONE;
16478         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16479             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16480             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16481             fromX == toX) {
16482             /* 2-square pawn move just happened */
16483             *p++ = toX + AAA;
16484             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16485         } else {
16486             *p++ = '-';
16487         }
16488     } else if(move == backwardMostMove) {
16489         // [HGM] perhaps we should always do it like this, and forget the above?
16490         if((signed char)boards[move][EP_STATUS] >= 0) {
16491             *p++ = boards[move][EP_STATUS] + AAA;
16492             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16493         } else {
16494             *p++ = '-';
16495         }
16496     } else {
16497         *p++ = '-';
16498     }
16499     *p++ = ' ';
16500   }
16501   }
16502
16503     /* [HGM] find reversible plies */
16504     {   int i = 0, j=move;
16505
16506         if (appData.debugMode) { int k;
16507             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16508             for(k=backwardMostMove; k<=forwardMostMove; k++)
16509                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16510
16511         }
16512
16513         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16514         if( j == backwardMostMove ) i += initialRulePlies;
16515         sprintf(p, "%d ", i);
16516         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16517     }
16518     /* Fullmove number */
16519     sprintf(p, "%d", (move / 2) + 1);
16520
16521     return StrSave(buf);
16522 }
16523
16524 Boolean
16525 ParseFEN(board, blackPlaysFirst, fen)
16526     Board board;
16527      int *blackPlaysFirst;
16528      char *fen;
16529 {
16530     int i, j;
16531     char *p, c;
16532     int emptycount;
16533     ChessSquare piece;
16534
16535     p = fen;
16536
16537     /* [HGM] by default clear Crazyhouse holdings, if present */
16538     if(gameInfo.holdingsWidth) {
16539        for(i=0; i<BOARD_HEIGHT; i++) {
16540            board[i][0]             = EmptySquare; /* black holdings */
16541            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16542            board[i][1]             = (ChessSquare) 0; /* black counts */
16543            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16544        }
16545     }
16546
16547     /* Piece placement data */
16548     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16549         j = 0;
16550         for (;;) {
16551             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16552                 if (*p == '/') p++;
16553                 emptycount = gameInfo.boardWidth - j;
16554                 while (emptycount--)
16555                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16556                 break;
16557 #if(BOARD_FILES >= 10)
16558             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16559                 p++; emptycount=10;
16560                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16561                 while (emptycount--)
16562                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16563 #endif
16564             } else if (isdigit(*p)) {
16565                 emptycount = *p++ - '0';
16566                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16567                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16568                 while (emptycount--)
16569                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16570             } else if (*p == '+' || isalpha(*p)) {
16571                 if (j >= gameInfo.boardWidth) return FALSE;
16572                 if(*p=='+') {
16573                     piece = CharToPiece(*++p);
16574                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16575                     piece = (ChessSquare) (PROMOTED piece ); p++;
16576                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16577                 } else piece = CharToPiece(*p++);
16578
16579                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16580                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16581                     piece = (ChessSquare) (PROMOTED piece);
16582                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16583                     p++;
16584                 }
16585                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16586             } else {
16587                 return FALSE;
16588             }
16589         }
16590     }
16591     while (*p == '/' || *p == ' ') p++;
16592
16593     /* [HGM] look for Crazyhouse holdings here */
16594     while(*p==' ') p++;
16595     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16596         if(*p == '[') p++;
16597         if(*p == '-' ) p++; /* empty holdings */ else {
16598             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16599             /* if we would allow FEN reading to set board size, we would   */
16600             /* have to add holdings and shift the board read so far here   */
16601             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16602                 p++;
16603                 if((int) piece >= (int) BlackPawn ) {
16604                     i = (int)piece - (int)BlackPawn;
16605                     i = PieceToNumber((ChessSquare)i);
16606                     if( i >= gameInfo.holdingsSize ) return FALSE;
16607                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16608                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16609                 } else {
16610                     i = (int)piece - (int)WhitePawn;
16611                     i = PieceToNumber((ChessSquare)i);
16612                     if( i >= gameInfo.holdingsSize ) return FALSE;
16613                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16614                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16615                 }
16616             }
16617         }
16618         if(*p == ']') p++;
16619     }
16620
16621     while(*p == ' ') p++;
16622
16623     /* Active color */
16624     c = *p++;
16625     if(appData.colorNickNames) {
16626       if( c == appData.colorNickNames[0] ) c = 'w'; else
16627       if( c == appData.colorNickNames[1] ) c = 'b';
16628     }
16629     switch (c) {
16630       case 'w':
16631         *blackPlaysFirst = FALSE;
16632         break;
16633       case 'b':
16634         *blackPlaysFirst = TRUE;
16635         break;
16636       default:
16637         return FALSE;
16638     }
16639
16640     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16641     /* return the extra info in global variiables             */
16642
16643     /* set defaults in case FEN is incomplete */
16644     board[EP_STATUS] = EP_UNKNOWN;
16645     for(i=0; i<nrCastlingRights; i++ ) {
16646         board[CASTLING][i] =
16647             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16648     }   /* assume possible unless obviously impossible */
16649     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16650     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16651     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16652                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16653     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16654     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16655     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16656                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16657     FENrulePlies = 0;
16658
16659     while(*p==' ') p++;
16660     if(nrCastlingRights) {
16661       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16662           /* castling indicator present, so default becomes no castlings */
16663           for(i=0; i<nrCastlingRights; i++ ) {
16664                  board[CASTLING][i] = NoRights;
16665           }
16666       }
16667       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16668              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16669              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16670              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16671         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16672
16673         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16674             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16675             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16676         }
16677         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16678             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16679         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16680                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16681         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16682                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16683         switch(c) {
16684           case'K':
16685               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16686               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16687               board[CASTLING][2] = whiteKingFile;
16688               break;
16689           case'Q':
16690               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16691               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16692               board[CASTLING][2] = whiteKingFile;
16693               break;
16694           case'k':
16695               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16696               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16697               board[CASTLING][5] = blackKingFile;
16698               break;
16699           case'q':
16700               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16701               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16702               board[CASTLING][5] = blackKingFile;
16703           case '-':
16704               break;
16705           default: /* FRC castlings */
16706               if(c >= 'a') { /* black rights */
16707                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16708                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16709                   if(i == BOARD_RGHT) break;
16710                   board[CASTLING][5] = i;
16711                   c -= AAA;
16712                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16713                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16714                   if(c > i)
16715                       board[CASTLING][3] = c;
16716                   else
16717                       board[CASTLING][4] = c;
16718               } else { /* white rights */
16719                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16720                     if(board[0][i] == WhiteKing) break;
16721                   if(i == BOARD_RGHT) break;
16722                   board[CASTLING][2] = i;
16723                   c -= AAA - 'a' + 'A';
16724                   if(board[0][c] >= WhiteKing) break;
16725                   if(c > i)
16726                       board[CASTLING][0] = c;
16727                   else
16728                       board[CASTLING][1] = c;
16729               }
16730         }
16731       }
16732       for(i=0; i<nrCastlingRights; i++)
16733         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16734     if (appData.debugMode) {
16735         fprintf(debugFP, "FEN castling rights:");
16736         for(i=0; i<nrCastlingRights; i++)
16737         fprintf(debugFP, " %d", board[CASTLING][i]);
16738         fprintf(debugFP, "\n");
16739     }
16740
16741       while(*p==' ') p++;
16742     }
16743
16744     /* read e.p. field in games that know e.p. capture */
16745     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16746        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16747       if(*p=='-') {
16748         p++; board[EP_STATUS] = EP_NONE;
16749       } else {
16750          char c = *p++ - AAA;
16751
16752          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16753          if(*p >= '0' && *p <='9') p++;
16754          board[EP_STATUS] = c;
16755       }
16756     }
16757
16758
16759     if(sscanf(p, "%d", &i) == 1) {
16760         FENrulePlies = i; /* 50-move ply counter */
16761         /* (The move number is still ignored)    */
16762     }
16763
16764     return TRUE;
16765 }
16766
16767 void
16768 EditPositionPasteFEN(char *fen)
16769 {
16770   if (fen != NULL) {
16771     Board initial_position;
16772
16773     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16774       DisplayError(_("Bad FEN position in clipboard"), 0);
16775       return ;
16776     } else {
16777       int savedBlackPlaysFirst = blackPlaysFirst;
16778       EditPositionEvent();
16779       blackPlaysFirst = savedBlackPlaysFirst;
16780       CopyBoard(boards[0], initial_position);
16781       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16782       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16783       DisplayBothClocks();
16784       DrawPosition(FALSE, boards[currentMove]);
16785     }
16786   }
16787 }
16788
16789 static char cseq[12] = "\\   ";
16790
16791 Boolean set_cont_sequence(char *new_seq)
16792 {
16793     int len;
16794     Boolean ret;
16795
16796     // handle bad attempts to set the sequence
16797         if (!new_seq)
16798                 return 0; // acceptable error - no debug
16799
16800     len = strlen(new_seq);
16801     ret = (len > 0) && (len < sizeof(cseq));
16802     if (ret)
16803       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16804     else if (appData.debugMode)
16805       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16806     return ret;
16807 }
16808
16809 /*
16810     reformat a source message so words don't cross the width boundary.  internal
16811     newlines are not removed.  returns the wrapped size (no null character unless
16812     included in source message).  If dest is NULL, only calculate the size required
16813     for the dest buffer.  lp argument indicats line position upon entry, and it's
16814     passed back upon exit.
16815 */
16816 int wrap(char *dest, char *src, int count, int width, int *lp)
16817 {
16818     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16819
16820     cseq_len = strlen(cseq);
16821     old_line = line = *lp;
16822     ansi = len = clen = 0;
16823
16824     for (i=0; i < count; i++)
16825     {
16826         if (src[i] == '\033')
16827             ansi = 1;
16828
16829         // if we hit the width, back up
16830         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16831         {
16832             // store i & len in case the word is too long
16833             old_i = i, old_len = len;
16834
16835             // find the end of the last word
16836             while (i && src[i] != ' ' && src[i] != '\n')
16837             {
16838                 i--;
16839                 len--;
16840             }
16841
16842             // word too long?  restore i & len before splitting it
16843             if ((old_i-i+clen) >= width)
16844             {
16845                 i = old_i;
16846                 len = old_len;
16847             }
16848
16849             // extra space?
16850             if (i && src[i-1] == ' ')
16851                 len--;
16852
16853             if (src[i] != ' ' && src[i] != '\n')
16854             {
16855                 i--;
16856                 if (len)
16857                     len--;
16858             }
16859
16860             // now append the newline and continuation sequence
16861             if (dest)
16862                 dest[len] = '\n';
16863             len++;
16864             if (dest)
16865                 strncpy(dest+len, cseq, cseq_len);
16866             len += cseq_len;
16867             line = cseq_len;
16868             clen = cseq_len;
16869             continue;
16870         }
16871
16872         if (dest)
16873             dest[len] = src[i];
16874         len++;
16875         if (!ansi)
16876             line++;
16877         if (src[i] == '\n')
16878             line = 0;
16879         if (src[i] == 'm')
16880             ansi = 0;
16881     }
16882     if (dest && appData.debugMode)
16883     {
16884         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16885             count, width, line, len, *lp);
16886         show_bytes(debugFP, src, count);
16887         fprintf(debugFP, "\ndest: ");
16888         show_bytes(debugFP, dest, len);
16889         fprintf(debugFP, "\n");
16890     }
16891     *lp = dest ? line : old_line;
16892
16893     return len;
16894 }
16895
16896 // [HGM] vari: routines for shelving variations
16897 Boolean modeRestore = FALSE;
16898
16899 void
16900 PushInner(int firstMove, int lastMove)
16901 {
16902         int i, j, nrMoves = lastMove - firstMove;
16903
16904         // push current tail of game on stack
16905         savedResult[storedGames] = gameInfo.result;
16906         savedDetails[storedGames] = gameInfo.resultDetails;
16907         gameInfo.resultDetails = NULL;
16908         savedFirst[storedGames] = firstMove;
16909         savedLast [storedGames] = lastMove;
16910         savedFramePtr[storedGames] = framePtr;
16911         framePtr -= nrMoves; // reserve space for the boards
16912         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16913             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16914             for(j=0; j<MOVE_LEN; j++)
16915                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16916             for(j=0; j<2*MOVE_LEN; j++)
16917                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16918             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16919             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16920             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16921             pvInfoList[firstMove+i-1].depth = 0;
16922             commentList[framePtr+i] = commentList[firstMove+i];
16923             commentList[firstMove+i] = NULL;
16924         }
16925
16926         storedGames++;
16927         forwardMostMove = firstMove; // truncate game so we can start variation
16928 }
16929
16930 void
16931 PushTail(int firstMove, int lastMove)
16932 {
16933         if(appData.icsActive) { // only in local mode
16934                 forwardMostMove = currentMove; // mimic old ICS behavior
16935                 return;
16936         }
16937         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16938
16939         PushInner(firstMove, lastMove);
16940         if(storedGames == 1) GreyRevert(FALSE);
16941         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16942 }
16943
16944 void
16945 PopInner(Boolean annotate)
16946 {
16947         int i, j, nrMoves;
16948         char buf[8000], moveBuf[20];
16949
16950         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16951         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16952         nrMoves = savedLast[storedGames] - currentMove;
16953         if(annotate) {
16954                 int cnt = 10;
16955                 if(!WhiteOnMove(currentMove))
16956                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16957                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16958                 for(i=currentMove; i<forwardMostMove; i++) {
16959                         if(WhiteOnMove(i))
16960                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16961                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16962                         strcat(buf, moveBuf);
16963                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16964                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16965                 }
16966                 strcat(buf, ")");
16967         }
16968         for(i=1; i<=nrMoves; i++) { // copy last variation back
16969             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16970             for(j=0; j<MOVE_LEN; j++)
16971                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16972             for(j=0; j<2*MOVE_LEN; j++)
16973                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16974             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16975             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16976             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16977             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16978             commentList[currentMove+i] = commentList[framePtr+i];
16979             commentList[framePtr+i] = NULL;
16980         }
16981         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16982         framePtr = savedFramePtr[storedGames];
16983         gameInfo.result = savedResult[storedGames];
16984         if(gameInfo.resultDetails != NULL) {
16985             free(gameInfo.resultDetails);
16986       }
16987         gameInfo.resultDetails = savedDetails[storedGames];
16988         forwardMostMove = currentMove + nrMoves;
16989 }
16990
16991 Boolean
16992 PopTail(Boolean annotate)
16993 {
16994         if(appData.icsActive) return FALSE; // only in local mode
16995         if(!storedGames) return FALSE; // sanity
16996         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16997
16998         PopInner(annotate);
16999         if(currentMove < forwardMostMove) ForwardEvent(); else
17000         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17001
17002         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17003         return TRUE;
17004 }
17005
17006 void
17007 CleanupTail()
17008 {       // remove all shelved variations
17009         int i;
17010         for(i=0; i<storedGames; i++) {
17011             if(savedDetails[i])
17012                 free(savedDetails[i]);
17013             savedDetails[i] = NULL;
17014         }
17015         for(i=framePtr; i<MAX_MOVES; i++) {
17016                 if(commentList[i]) free(commentList[i]);
17017                 commentList[i] = NULL;
17018         }
17019         framePtr = MAX_MOVES-1;
17020         storedGames = 0;
17021 }
17022
17023 void
17024 LoadVariation(int index, char *text)
17025 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17026         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17027         int level = 0, move;
17028
17029         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17030         // first find outermost bracketing variation
17031         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17032             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17033                 if(*p == '{') wait = '}'; else
17034                 if(*p == '[') wait = ']'; else
17035                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17036                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17037             }
17038             if(*p == wait) wait = NULLCHAR; // closing ]} found
17039             p++;
17040         }
17041         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17042         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17043         end[1] = NULLCHAR; // clip off comment beyond variation
17044         ToNrEvent(currentMove-1);
17045         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17046         // kludge: use ParsePV() to append variation to game
17047         move = currentMove;
17048         ParsePV(start, TRUE, TRUE);
17049         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17050         ClearPremoveHighlights();
17051         CommentPopDown();
17052         ToNrEvent(currentMove+1);
17053 }
17054