6520238f8d03a979bb23a60d31ffc2885fe736c3
[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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237 void DisplayTwoMachinesTitle P(());
238
239 #ifdef WIN32
240        extern void ConsoleCreate();
241 #endif
242
243 ChessProgramState *WhitePlayer();
244 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
245 int VerifyDisplayMode P(());
246
247 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
248 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
249 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
250 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
251 void ics_update_width P((int new_width));
252 extern char installDir[MSG_SIZ];
253 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 Boolean abortMatch;
255
256 extern int tinyLayout, smallLayout;
257 ChessProgramStats programStats;
258 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 int endPV = -1;
260 static int exiting = 0; /* [HGM] moved to top */
261 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
262 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
263 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
264 int partnerHighlight[2];
265 Boolean partnerBoardValid = 0;
266 char partnerStatus[MSG_SIZ];
267 Boolean partnerUp;
268 Boolean originalFlip;
269 Boolean twoBoards = 0;
270 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
271 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
272 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
273 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
274 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
275 int opponentKibitzes;
276 int lastSavedGame; /* [HGM] save: ID of game */
277 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
278 extern int chatCount;
279 int chattingPartner;
280 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
281 char lastMsg[MSG_SIZ];
282 ChessSquare pieceSweep = EmptySquare;
283 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
284 int promoDefaultAltered;
285
286 /* States for ics_getting_history */
287 #define H_FALSE 0
288 #define H_REQUESTED 1
289 #define H_GOT_REQ_HEADER 2
290 #define H_GOT_UNREQ_HEADER 3
291 #define H_GETTING_MOVES 4
292 #define H_GOT_UNWANTED_HEADER 5
293
294 /* whosays values for GameEnds */
295 #define GE_ICS 0
296 #define GE_ENGINE 1
297 #define GE_PLAYER 2
298 #define GE_FILE 3
299 #define GE_XBOARD 4
300 #define GE_ENGINE1 5
301 #define GE_ENGINE2 6
302
303 /* Maximum number of games in a cmail message */
304 #define CMAIL_MAX_GAMES 20
305
306 /* Different types of move when calling RegisterMove */
307 #define CMAIL_MOVE   0
308 #define CMAIL_RESIGN 1
309 #define CMAIL_DRAW   2
310 #define CMAIL_ACCEPT 3
311
312 /* Different types of result to remember for each game */
313 #define CMAIL_NOT_RESULT 0
314 #define CMAIL_OLD_RESULT 1
315 #define CMAIL_NEW_RESULT 2
316
317 /* Telnet protocol constants */
318 #define TN_WILL 0373
319 #define TN_WONT 0374
320 #define TN_DO   0375
321 #define TN_DONT 0376
322 #define TN_IAC  0377
323 #define TN_ECHO 0001
324 #define TN_SGA  0003
325 #define TN_PORT 23
326
327 char*
328 safeStrCpy( char *dst, const char *src, size_t count )
329 { // [HGM] made safe
330   int i;
331   assert( dst != NULL );
332   assert( src != NULL );
333   assert( count > 0 );
334
335   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
336   if(  i == count && dst[count-1] != NULLCHAR)
337     {
338       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
339       if(appData.debugMode)
340       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
341     }
342
343   return dst;
344 }
345
346 /* Some compiler can't cast u64 to double
347  * This function do the job for us:
348
349  * We use the highest bit for cast, this only
350  * works if the highest bit is not
351  * in use (This should not happen)
352  *
353  * We used this for all compiler
354  */
355 double
356 u64ToDouble(u64 value)
357 {
358   double r;
359   u64 tmp = value & u64Const(0x7fffffffffffffff);
360   r = (double)(s64)tmp;
361   if (value & u64Const(0x8000000000000000))
362        r +=  9.2233720368547758080e18; /* 2^63 */
363  return r;
364 }
365
366 /* Fake up flags for now, as we aren't keeping track of castling
367    availability yet. [HGM] Change of logic: the flag now only
368    indicates the type of castlings allowed by the rule of the game.
369    The actual rights themselves are maintained in the array
370    castlingRights, as part of the game history, and are not probed
371    by this function.
372  */
373 int
374 PosFlags(index)
375 {
376   int flags = F_ALL_CASTLE_OK;
377   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
378   switch (gameInfo.variant) {
379   case VariantSuicide:
380     flags &= ~F_ALL_CASTLE_OK;
381   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
382     flags |= F_IGNORE_CHECK;
383   case VariantLosers:
384     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385     break;
386   case VariantAtomic:
387     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388     break;
389   case VariantKriegspiel:
390     flags |= F_KRIEGSPIEL_CAPTURE;
391     break;
392   case VariantCapaRandom:
393   case VariantFischeRandom:
394     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
395   case VariantNoCastle:
396   case VariantShatranj:
397   case VariantCourier:
398   case VariantMakruk:
399     flags &= ~F_ALL_CASTLE_OK;
400     break;
401   default:
402     break;
403   }
404   return flags;
405 }
406
407 FILE *gameFileFP, *debugFP;
408
409 /*
410     [AS] Note: sometimes, the sscanf() function is used to parse the input
411     into a fixed-size buffer. Because of this, we must be prepared to
412     receive strings as long as the size of the input buffer, which is currently
413     set to 4K for Windows and 8K for the rest.
414     So, we must either allocate sufficiently large buffers here, or
415     reduce the size of the input buffer in the input reading part.
416 */
417
418 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
419 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
420 char thinkOutput1[MSG_SIZ*10];
421
422 ChessProgramState first, second, pairing;
423
424 /* premove variables */
425 int premoveToX = 0;
426 int premoveToY = 0;
427 int premoveFromX = 0;
428 int premoveFromY = 0;
429 int premovePromoChar = 0;
430 int gotPremove = 0;
431 Boolean alarmSounded;
432 /* end premove variables */
433
434 char *ics_prefix = "$";
435 int ics_type = ICS_GENERIC;
436
437 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
438 int pauseExamForwardMostMove = 0;
439 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
440 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
441 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
442 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
443 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
444 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
445 int whiteFlag = FALSE, blackFlag = FALSE;
446 int userOfferedDraw = FALSE;
447 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
448 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
449 int cmailMoveType[CMAIL_MAX_GAMES];
450 long ics_clock_paused = 0;
451 ProcRef icsPR = NoProc, cmailPR = NoProc;
452 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
453 GameMode gameMode = BeginningOfGame;
454 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
455 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
456 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
457 int hiddenThinkOutputState = 0; /* [AS] */
458 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
459 int adjudicateLossPlies = 6;
460 char white_holding[64], black_holding[64];
461 TimeMark lastNodeCountTime;
462 long lastNodeCount=0;
463 int shiftKey; // [HGM] set by mouse handler
464
465 int have_sent_ICS_logon = 0;
466 int movesPerSession;
467 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
468 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
469 long timeControl_2; /* [AS] Allow separate time controls */
470 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
471 long timeRemaining[2][MAX_MOVES];
472 int matchGame = 0, nextGame = 0, roundNr = 0;
473 Boolean waitingForGame = FALSE;
474 TimeMark programStartTime, pauseStart;
475 char ics_handle[MSG_SIZ];
476 int have_set_title = 0;
477
478 /* animateTraining preserves the state of appData.animate
479  * when Training mode is activated. This allows the
480  * response to be animated when appData.animate == TRUE and
481  * appData.animateDragging == TRUE.
482  */
483 Boolean animateTraining;
484
485 GameInfo gameInfo;
486
487 AppData appData;
488
489 Board boards[MAX_MOVES];
490 /* [HGM] Following 7 needed for accurate legality tests: */
491 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
492 signed char  initialRights[BOARD_FILES];
493 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
494 int   initialRulePlies, FENrulePlies;
495 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int loadFlag = 0;
497 Boolean shuffleOpenings;
498 int mute; // mute all sounds
499
500 // [HGM] vari: next 12 to save and restore variations
501 #define MAX_VARIATIONS 10
502 int framePtr = MAX_MOVES-1; // points to free stack entry
503 int storedGames = 0;
504 int savedFirst[MAX_VARIATIONS];
505 int savedLast[MAX_VARIATIONS];
506 int savedFramePtr[MAX_VARIATIONS];
507 char *savedDetails[MAX_VARIATIONS];
508 ChessMove savedResult[MAX_VARIATIONS];
509
510 void PushTail P((int firstMove, int lastMove));
511 Boolean PopTail P((Boolean annotate));
512 void PushInner P((int firstMove, int lastMove));
513 void PopInner P((Boolean annotate));
514 void CleanupTail P((void));
515
516 ChessSquare  FIDEArray[2][BOARD_FILES] = {
517     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520         BlackKing, BlackBishop, BlackKnight, BlackRook }
521 };
522
523 ChessSquare twoKingsArray[2][BOARD_FILES] = {
524     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
525         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
526     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
527         BlackKing, BlackKing, BlackKnight, BlackRook }
528 };
529
530 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
531     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
532         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
533     { BlackRook, BlackMan, BlackBishop, BlackQueen,
534         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 };
536
537 ChessSquare SpartanArray[2][BOARD_FILES] = {
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
541         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 };
543
544 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
548         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 };
550
551 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
553         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
555         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 };
557
558 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
559     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackMan, BlackFerz,
562         BlackKing, BlackMan, BlackKnight, BlackRook }
563 };
564
565
566 #if (BOARD_FILES>=10)
567 ChessSquare ShogiArray[2][BOARD_FILES] = {
568     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
569         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
570     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
571         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 };
573
574 ChessSquare XiangqiArray[2][BOARD_FILES] = {
575     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
576         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
578         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 };
580
581 ChessSquare CapablancaArray[2][BOARD_FILES] = {
582     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
583         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
585         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 };
587
588 ChessSquare GreatArray[2][BOARD_FILES] = {
589     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
590         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
591     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
592         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 };
594
595 ChessSquare JanusArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
597         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
598     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
599         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
600 };
601
602 #ifdef GOTHIC
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !GOTHIC
610 #define GothicArray CapablancaArray
611 #endif // !GOTHIC
612
613 #ifdef FALCON
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
616         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
618         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !FALCON
621 #define FalconArray CapablancaArray
622 #endif // !FALCON
623
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
630
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 };
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
641
642
643 Board initialPosition;
644
645
646 /* Convert str to a rating. Checks for special cases of "----",
647
648    "++++", etc. Also strips ()'s */
649 int
650 string_to_rating(str)
651   char *str;
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine(ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions(ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741 "first",
742 "second"
743 };
744
745 void
746 InitEngine(ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len > MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833 }
834
835 ChessProgramState *savCps;
836
837 void
838 LoadEngine()
839 {
840     int i;
841     if(WaitForEngine(savCps, LoadEngine)) return;
842     CommonEngineInit(); // recalculate time odds
843     if(gameInfo.variant != StringToVariant(appData.variant)) {
844         // we changed variant when loading the engine; this forces us to reset
845         Reset(TRUE, savCps != &first);
846         EditGameEvent(); // for consistency with other path, as Reset changes mode
847     }
848     InitChessProgram(savCps, FALSE);
849     SendToProgram("force\n", savCps);
850     DisplayMessage("", "");
851     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
852     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
853     ThawUI();
854     SetGNUMode();
855 }
856
857 void
858 ReplaceEngine(ChessProgramState *cps, int n)
859 {
860     EditGameEvent();
861     UnloadEngine(cps);
862     appData.noChessProgram = FALSE;
863     appData.clockMode = TRUE;
864     InitEngine(cps, n);
865     UpdateLogos(TRUE);
866     if(n) return; // only startup first engine immediately; second can wait
867     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
868     LoadEngine();
869 }
870
871 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
872 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
873
874 static char resetOptions[] = 
875         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
876         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877
878 void
879 Load(ChessProgramState *cps, int i)
880 {
881     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
882     if(engineLine[0]) { // an engine was selected from the combo box
883         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
884         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
885         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
886         ParseArgsFromString(buf);
887         SwapEngines(i);
888         ReplaceEngine(cps, i);
889         return;
890     }
891     p = engineName;
892     while(q = strchr(p, SLASH)) p = q+1;
893     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
894     if(engineDir[0] != NULLCHAR)
895         appData.directory[i] = engineDir;
896     else if(p != engineName) { // derive directory from engine path, when not given
897         p[-1] = 0;
898         appData.directory[i] = strdup(engineName);
899         p[-1] = SLASH;
900     } else appData.directory[i] = ".";
901     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
902     if(params[0]) {
903         snprintf(command, MSG_SIZ, "%s %s", p, params);
904         p = command;
905     }
906     appData.chessProgram[i] = strdup(p);
907     appData.isUCI[i] = isUCI;
908     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
909     appData.hasOwnBookUCI[i] = hasBook;
910     if(!nickName[0]) useNick = FALSE;
911     if(useNick) ASSIGN(appData.pgnName[i], nickName);
912     if(addToList) {
913         int len;
914         char quote;
915         q = firstChessProgramNames;
916         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
917         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
918         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
919                         quote, p, quote, appData.directory[i], 
920                         useNick ? " -fn \"" : "",
921                         useNick ? nickName : "",
922                         useNick ? "\"" : "",
923                         v1 ? " -firstProtocolVersion 1" : "",
924                         hasBook ? "" : " -fNoOwnBookUCI",
925                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
926                         storeVariant ? " -variant " : "",
927                         storeVariant ? VariantName(gameInfo.variant) : "");
928         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
929         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
930         if(q)   free(q);
931     }
932     ReplaceEngine(cps, i);
933 }
934
935 void
936 InitTimeControls()
937 {
938     int matched, min, sec;
939     /*
940      * Parse timeControl resource
941      */
942     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
943                           appData.movesPerSession)) {
944         char buf[MSG_SIZ];
945         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
946         DisplayFatalError(buf, 0, 2);
947     }
948
949     /*
950      * Parse searchTime resource
951      */
952     if (*appData.searchTime != NULLCHAR) {
953         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
954         if (matched == 1) {
955             searchTime = min * 60;
956         } else if (matched == 2) {
957             searchTime = min * 60 + sec;
958         } else {
959             char buf[MSG_SIZ];
960             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
961             DisplayFatalError(buf, 0, 2);
962         }
963     }
964 }
965
966 void
967 InitBackEnd1()
968 {
969
970     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
971     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
972
973     GetTimeMark(&programStartTime);
974     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
975     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976
977     ClearProgramStats();
978     programStats.ok_to_send = 1;
979     programStats.seen_stat = 0;
980
981     /*
982      * Initialize game list
983      */
984     ListNew(&gameList);
985
986
987     /*
988      * Internet chess server status
989      */
990     if (appData.icsActive) {
991         appData.matchMode = FALSE;
992         appData.matchGames = 0;
993 #if ZIPPY
994         appData.noChessProgram = !appData.zippyPlay;
995 #else
996         appData.zippyPlay = FALSE;
997         appData.zippyTalk = FALSE;
998         appData.noChessProgram = TRUE;
999 #endif
1000         if (*appData.icsHelper != NULLCHAR) {
1001             appData.useTelnet = TRUE;
1002             appData.telnetProgram = appData.icsHelper;
1003         }
1004     } else {
1005         appData.zippyTalk = appData.zippyPlay = FALSE;
1006     }
1007
1008     /* [AS] Initialize pv info list [HGM] and game state */
1009     {
1010         int i, j;
1011
1012         for( i=0; i<=framePtr; i++ ) {
1013             pvInfoList[i].depth = -1;
1014             boards[i][EP_STATUS] = EP_NONE;
1015             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1016         }
1017     }
1018
1019     InitTimeControls();
1020
1021     /* [AS] Adjudication threshold */
1022     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1023
1024     InitEngine(&first, 0);
1025     InitEngine(&second, 1);
1026     CommonEngineInit();
1027
1028     pairing.which = "pairing"; // pairing engine
1029     pairing.pr = NoProc;
1030     pairing.isr = NULL;
1031     pairing.program = appData.pairingEngine;
1032     pairing.host = "localhost";
1033     pairing.dir = ".";
1034
1035     if (appData.icsActive) {
1036         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1037     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1038         appData.clockMode = FALSE;
1039         first.sendTime = second.sendTime = 0;
1040     }
1041
1042 #if ZIPPY
1043     /* Override some settings from environment variables, for backward
1044        compatibility.  Unfortunately it's not feasible to have the env
1045        vars just set defaults, at least in xboard.  Ugh.
1046     */
1047     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1048       ZippyInit();
1049     }
1050 #endif
1051
1052     if (!appData.icsActive) {
1053       char buf[MSG_SIZ];
1054       int len;
1055
1056       /* Check for variants that are supported only in ICS mode,
1057          or not at all.  Some that are accepted here nevertheless
1058          have bugs; see comments below.
1059       */
1060       VariantClass variant = StringToVariant(appData.variant);
1061       switch (variant) {
1062       case VariantBughouse:     /* need four players and two boards */
1063       case VariantKriegspiel:   /* need to hide pieces and move details */
1064         /* case VariantFischeRandom: (Fabien: moved below) */
1065         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1066         if( (len > MSG_SIZ) && appData.debugMode )
1067           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068
1069         DisplayFatalError(buf, 0, 2);
1070         return;
1071
1072       case VariantUnknown:
1073       case VariantLoadable:
1074       case Variant29:
1075       case Variant30:
1076       case Variant31:
1077       case Variant32:
1078       case Variant33:
1079       case Variant34:
1080       case Variant35:
1081       case Variant36:
1082       default:
1083         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1084         if( (len > MSG_SIZ) && appData.debugMode )
1085           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1086
1087         DisplayFatalError(buf, 0, 2);
1088         return;
1089
1090       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1091       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1092       case VariantGothic:     /* [HGM] should work */
1093       case VariantCapablanca: /* [HGM] should work */
1094       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1095       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1096       case VariantKnightmate: /* [HGM] should work */
1097       case VariantCylinder:   /* [HGM] untested */
1098       case VariantFalcon:     /* [HGM] untested */
1099       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1100                                  offboard interposition not understood */
1101       case VariantNormal:     /* definitely works! */
1102       case VariantWildCastle: /* pieces not automatically shuffled */
1103       case VariantNoCastle:   /* pieces not automatically shuffled */
1104       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1105       case VariantLosers:     /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantSuicide:    /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantGiveaway:   /* should work except for win condition,
1110                                  and doesn't know captures are mandatory */
1111       case VariantTwoKings:   /* should work */
1112       case VariantAtomic:     /* should work except for win condition */
1113       case Variant3Check:     /* should work except for win condition */
1114       case VariantShatranj:   /* should work except for all win conditions */
1115       case VariantMakruk:     /* should work except for daw countdown */
1116       case VariantBerolina:   /* might work if TestLegality is off */
1117       case VariantCapaRandom: /* should work */
1118       case VariantJanus:      /* should work */
1119       case VariantSuper:      /* experimental */
1120       case VariantGreat:      /* experimental, requires legality testing to be off */
1121       case VariantSChess:     /* S-Chess, 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)) { // 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\r
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 /*
1658  * Establish will establish a contact to a remote host.port.
1659  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1660  *  used to talk to the host.
1661  * Returns 0 if okay, error code if not.
1662  */
1663 int
1664 establish()
1665 {
1666     char buf[MSG_SIZ];
1667
1668     if (*appData.icsCommPort != NULLCHAR) {
1669         /* Talk to the host through a serial comm port */
1670         return OpenCommPort(appData.icsCommPort, &icsPR);
1671
1672     } else if (*appData.gateway != NULLCHAR) {
1673         if (*appData.remoteShell == NULLCHAR) {
1674             /* Use the rcmd protocol to run telnet program on a gateway host */
1675             snprintf(buf, sizeof(buf), "%s %s %s",
1676                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1677             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1678
1679         } else {
1680             /* Use the rsh program to run telnet program on a gateway host */
1681             if (*appData.remoteUser == NULLCHAR) {
1682                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1683                         appData.gateway, appData.telnetProgram,
1684                         appData.icsHost, appData.icsPort);
1685             } else {
1686                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1687                         appData.remoteShell, appData.gateway,
1688                         appData.remoteUser, appData.telnetProgram,
1689                         appData.icsHost, appData.icsPort);
1690             }
1691             return StartChildProcess(buf, "", &icsPR);
1692
1693         }
1694     } else if (appData.useTelnet) {
1695         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1696
1697     } else {
1698         /* TCP socket interface differs somewhat between
1699            Unix and NT; handle details in the front end.
1700            */
1701         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1702     }
1703 }
1704
1705 void EscapeExpand(char *p, char *q)
1706 {       // [HGM] initstring: routine to shape up string arguments
1707         while(*p++ = *q++) if(p[-1] == '\\')
1708             switch(*q++) {
1709                 case 'n': p[-1] = '\n'; break;
1710                 case 'r': p[-1] = '\r'; break;
1711                 case 't': p[-1] = '\t'; break;
1712                 case '\\': p[-1] = '\\'; break;
1713                 case 0: *p = 0; return;
1714                 default: p[-1] = q[-1]; break;
1715             }
1716 }
1717
1718 void
1719 show_bytes(fp, buf, count)
1720      FILE *fp;
1721      char *buf;
1722      int count;
1723 {
1724     while (count--) {
1725         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1726             fprintf(fp, "\\%03o", *buf & 0xff);
1727         } else {
1728             putc(*buf, fp);
1729         }
1730         buf++;
1731     }
1732     fflush(fp);
1733 }
1734
1735 /* Returns an errno value */
1736 int
1737 OutputMaybeTelnet(pr, message, count, outError)
1738      ProcRef pr;
1739      char *message;
1740      int count;
1741      int *outError;
1742 {
1743     char buf[8192], *p, *q, *buflim;
1744     int left, newcount, outcount;
1745
1746     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1747         *appData.gateway != NULLCHAR) {
1748         if (appData.debugMode) {
1749             fprintf(debugFP, ">ICS: ");
1750             show_bytes(debugFP, message, count);
1751             fprintf(debugFP, "\n");
1752         }
1753         return OutputToProcess(pr, message, count, outError);
1754     }
1755
1756     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1757     p = message;
1758     q = buf;
1759     left = count;
1760     newcount = 0;
1761     while (left) {
1762         if (q >= buflim) {
1763             if (appData.debugMode) {
1764                 fprintf(debugFP, ">ICS: ");
1765                 show_bytes(debugFP, buf, newcount);
1766                 fprintf(debugFP, "\n");
1767             }
1768             outcount = OutputToProcess(pr, buf, newcount, outError);
1769             if (outcount < newcount) return -1; /* to be sure */
1770             q = buf;
1771             newcount = 0;
1772         }
1773         if (*p == '\n') {
1774             *q++ = '\r';
1775             newcount++;
1776         } else if (((unsigned char) *p) == TN_IAC) {
1777             *q++ = (char) TN_IAC;
1778             newcount ++;
1779         }
1780         *q++ = *p++;
1781         newcount++;
1782         left--;
1783     }
1784     if (appData.debugMode) {
1785         fprintf(debugFP, ">ICS: ");
1786         show_bytes(debugFP, buf, newcount);
1787         fprintf(debugFP, "\n");
1788     }
1789     outcount = OutputToProcess(pr, buf, newcount, outError);
1790     if (outcount < newcount) return -1; /* to be sure */
1791     return count;
1792 }
1793
1794 void
1795 read_from_player(isr, closure, message, count, error)
1796      InputSourceRef isr;
1797      VOIDSTAR closure;
1798      char *message;
1799      int count;
1800      int error;
1801 {
1802     int outError, outCount;
1803     static int gotEof = 0;
1804
1805     /* Pass data read from player on to ICS */
1806     if (count > 0) {
1807         gotEof = 0;
1808         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1809         if (outCount < count) {
1810             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1811         }
1812     } else if (count < 0) {
1813         RemoveInputSource(isr);
1814         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1815     } else if (gotEof++ > 0) {
1816         RemoveInputSource(isr);
1817         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1818     }
1819 }
1820
1821 void
1822 KeepAlive()
1823 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1824     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1825     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1826     SendToICS("date\n");
1827     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1828 }
1829
1830 /* added routine for printf style output to ics */
1831 void ics_printf(char *format, ...)
1832 {
1833     char buffer[MSG_SIZ];
1834     va_list args;
1835
1836     va_start(args, format);
1837     vsnprintf(buffer, sizeof(buffer), format, args);
1838     buffer[sizeof(buffer)-1] = '\0';
1839     SendToICS(buffer);
1840     va_end(args);
1841 }
1842
1843 void
1844 SendToICS(s)
1845      char *s;
1846 {
1847     int count, outCount, outError;
1848
1849     if (icsPR == NULL) return;
1850
1851     count = strlen(s);
1852     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1853     if (outCount < count) {
1854         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1855     }
1856 }
1857
1858 /* This is used for sending logon scripts to the ICS. Sending
1859    without a delay causes problems when using timestamp on ICC
1860    (at least on my machine). */
1861 void
1862 SendToICSDelayed(s,msdelay)
1863      char *s;
1864      long msdelay;
1865 {
1866     int count, outCount, outError;
1867
1868     if (icsPR == NULL) return;
1869
1870     count = strlen(s);
1871     if (appData.debugMode) {
1872         fprintf(debugFP, ">ICS: ");
1873         show_bytes(debugFP, s, count);
1874         fprintf(debugFP, "\n");
1875     }
1876     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877                                       msdelay);
1878     if (outCount < count) {
1879         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880     }
1881 }
1882
1883
1884 /* Remove all highlighting escape sequences in s
1885    Also deletes any suffix starting with '('
1886    */
1887 char *
1888 StripHighlightAndTitle(s)
1889      char *s;
1890 {
1891     static char retbuf[MSG_SIZ];
1892     char *p = retbuf;
1893
1894     while (*s != NULLCHAR) {
1895         while (*s == '\033') {
1896             while (*s != NULLCHAR && !isalpha(*s)) s++;
1897             if (*s != NULLCHAR) s++;
1898         }
1899         while (*s != NULLCHAR && *s != '\033') {
1900             if (*s == '(' || *s == '[') {
1901                 *p = NULLCHAR;
1902                 return retbuf;
1903             }
1904             *p++ = *s++;
1905         }
1906     }
1907     *p = NULLCHAR;
1908     return retbuf;
1909 }
1910
1911 /* Remove all highlighting escape sequences in s */
1912 char *
1913 StripHighlight(s)
1914      char *s;
1915 {
1916     static char retbuf[MSG_SIZ];
1917     char *p = retbuf;
1918
1919     while (*s != NULLCHAR) {
1920         while (*s == '\033') {
1921             while (*s != NULLCHAR && !isalpha(*s)) s++;
1922             if (*s != NULLCHAR) s++;
1923         }
1924         while (*s != NULLCHAR && *s != '\033') {
1925             *p++ = *s++;
1926         }
1927     }
1928     *p = NULLCHAR;
1929     return retbuf;
1930 }
1931
1932 char *variantNames[] = VARIANT_NAMES;
1933 char *
1934 VariantName(v)
1935      VariantClass v;
1936 {
1937     return variantNames[v];
1938 }
1939
1940
1941 /* Identify a variant from the strings the chess servers use or the
1942    PGN Variant tag names we use. */
1943 VariantClass
1944 StringToVariant(e)
1945      char *e;
1946 {
1947     char *p;
1948     int wnum = -1;
1949     VariantClass v = VariantNormal;
1950     int i, found = FALSE;
1951     char buf[MSG_SIZ];
1952     int len;
1953
1954     if (!e) return v;
1955
1956     /* [HGM] skip over optional board-size prefixes */
1957     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1958         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1959         while( *e++ != '_');
1960     }
1961
1962     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1963         v = VariantNormal;
1964         found = TRUE;
1965     } else
1966     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1967       if (StrCaseStr(e, variantNames[i])) {
1968         v = (VariantClass) i;
1969         found = TRUE;
1970         break;
1971       }
1972     }
1973
1974     if (!found) {
1975       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1976           || StrCaseStr(e, "wild/fr")
1977           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1978         v = VariantFischeRandom;
1979       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1980                  (i = 1, p = StrCaseStr(e, "w"))) {
1981         p += i;
1982         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1983         if (isdigit(*p)) {
1984           wnum = atoi(p);
1985         } else {
1986           wnum = -1;
1987         }
1988         switch (wnum) {
1989         case 0: /* FICS only, actually */
1990         case 1:
1991           /* Castling legal even if K starts on d-file */
1992           v = VariantWildCastle;
1993           break;
1994         case 2:
1995         case 3:
1996         case 4:
1997           /* Castling illegal even if K & R happen to start in
1998              normal positions. */
1999           v = VariantNoCastle;
2000           break;
2001         case 5:
2002         case 7:
2003         case 8:
2004         case 10:
2005         case 11:
2006         case 12:
2007         case 13:
2008         case 14:
2009         case 15:
2010         case 18:
2011         case 19:
2012           /* Castling legal iff K & R start in normal positions */
2013           v = VariantNormal;
2014           break;
2015         case 6:
2016         case 20:
2017         case 21:
2018           /* Special wilds for position setup; unclear what to do here */
2019           v = VariantLoadable;
2020           break;
2021         case 9:
2022           /* Bizarre ICC game */
2023           v = VariantTwoKings;
2024           break;
2025         case 16:
2026           v = VariantKriegspiel;
2027           break;
2028         case 17:
2029           v = VariantLosers;
2030           break;
2031         case 22:
2032           v = VariantFischeRandom;
2033           break;
2034         case 23:
2035           v = VariantCrazyhouse;
2036           break;
2037         case 24:
2038           v = VariantBughouse;
2039           break;
2040         case 25:
2041           v = Variant3Check;
2042           break;
2043         case 26:
2044           /* Not quite the same as FICS suicide! */
2045           v = VariantGiveaway;
2046           break;
2047         case 27:
2048           v = VariantAtomic;
2049           break;
2050         case 28:
2051           v = VariantShatranj;
2052           break;
2053
2054         /* Temporary names for future ICC types.  The name *will* change in
2055            the next xboard/WinBoard release after ICC defines it. */
2056         case 29:
2057           v = Variant29;
2058           break;
2059         case 30:
2060           v = Variant30;
2061           break;
2062         case 31:
2063           v = Variant31;
2064           break;
2065         case 32:
2066           v = Variant32;
2067           break;
2068         case 33:
2069           v = Variant33;
2070           break;
2071         case 34:
2072           v = Variant34;
2073           break;
2074         case 35:
2075           v = Variant35;
2076           break;
2077         case 36:
2078           v = Variant36;
2079           break;
2080         case 37:
2081           v = VariantShogi;
2082           break;
2083         case 38:
2084           v = VariantXiangqi;
2085           break;
2086         case 39:
2087           v = VariantCourier;
2088           break;
2089         case 40:
2090           v = VariantGothic;
2091           break;
2092         case 41:
2093           v = VariantCapablanca;
2094           break;
2095         case 42:
2096           v = VariantKnightmate;
2097           break;
2098         case 43:
2099           v = VariantFairy;
2100           break;
2101         case 44:
2102           v = VariantCylinder;
2103           break;
2104         case 45:
2105           v = VariantFalcon;
2106           break;
2107         case 46:
2108           v = VariantCapaRandom;
2109           break;
2110         case 47:
2111           v = VariantBerolina;
2112           break;
2113         case 48:
2114           v = VariantJanus;
2115           break;
2116         case 49:
2117           v = VariantSuper;
2118           break;
2119         case 50:
2120           v = VariantGreat;
2121           break;
2122         case -1:
2123           /* Found "wild" or "w" in the string but no number;
2124              must assume it's normal chess. */
2125           v = VariantNormal;
2126           break;
2127         default:
2128           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2129           if( (len > MSG_SIZ) && appData.debugMode )
2130             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2131
2132           DisplayError(buf, 0);
2133           v = VariantUnknown;
2134           break;
2135         }
2136       }
2137     }
2138     if (appData.debugMode) {
2139       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2140               e, wnum, VariantName(v));
2141     }
2142     return v;
2143 }
2144
2145 static int leftover_start = 0, leftover_len = 0;
2146 char star_match[STAR_MATCH_N][MSG_SIZ];
2147
2148 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2149    advance *index beyond it, and set leftover_start to the new value of
2150    *index; else return FALSE.  If pattern contains the character '*', it
2151    matches any sequence of characters not containing '\r', '\n', or the
2152    character following the '*' (if any), and the matched sequence(s) are
2153    copied into star_match.
2154    */
2155 int
2156 looking_at(buf, index, pattern)
2157      char *buf;
2158      int *index;
2159      char *pattern;
2160 {
2161     char *bufp = &buf[*index], *patternp = pattern;
2162     int star_count = 0;
2163     char *matchp = star_match[0];
2164
2165     for (;;) {
2166         if (*patternp == NULLCHAR) {
2167             *index = leftover_start = bufp - buf;
2168             *matchp = NULLCHAR;
2169             return TRUE;
2170         }
2171         if (*bufp == NULLCHAR) return FALSE;
2172         if (*patternp == '*') {
2173             if (*bufp == *(patternp + 1)) {
2174                 *matchp = NULLCHAR;
2175                 matchp = star_match[++star_count];
2176                 patternp += 2;
2177                 bufp++;
2178                 continue;
2179             } else if (*bufp == '\n' || *bufp == '\r') {
2180                 patternp++;
2181                 if (*patternp == NULLCHAR)
2182                   continue;
2183                 else
2184                   return FALSE;
2185             } else {
2186                 *matchp++ = *bufp++;
2187                 continue;
2188             }
2189         }
2190         if (*patternp != *bufp) return FALSE;
2191         patternp++;
2192         bufp++;
2193     }
2194 }
2195
2196 void
2197 SendToPlayer(data, length)
2198      char *data;
2199      int length;
2200 {
2201     int error, outCount;
2202     outCount = OutputToProcess(NoProc, data, length, &error);
2203     if (outCount < length) {
2204         DisplayFatalError(_("Error writing to display"), error, 1);
2205     }
2206 }
2207
2208 void
2209 PackHolding(packed, holding)
2210      char packed[];
2211      char *holding;
2212 {
2213     char *p = holding;
2214     char *q = packed;
2215     int runlength = 0;
2216     int curr = 9999;
2217     do {
2218         if (*p == curr) {
2219             runlength++;
2220         } else {
2221             switch (runlength) {
2222               case 0:
2223                 break;
2224               case 1:
2225                 *q++ = curr;
2226                 break;
2227               case 2:
2228                 *q++ = curr;
2229                 *q++ = curr;
2230                 break;
2231               default:
2232                 sprintf(q, "%d", runlength);
2233                 while (*q) q++;
2234                 *q++ = curr;
2235                 break;
2236             }
2237             runlength = 1;
2238             curr = *p;
2239         }
2240     } while (*p++);
2241     *q = NULLCHAR;
2242 }
2243
2244 /* Telnet protocol requests from the front end */
2245 void
2246 TelnetRequest(ddww, option)
2247      unsigned char ddww, option;
2248 {
2249     unsigned char msg[3];
2250     int outCount, outError;
2251
2252     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2253
2254     if (appData.debugMode) {
2255         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2256         switch (ddww) {
2257           case TN_DO:
2258             ddwwStr = "DO";
2259             break;
2260           case TN_DONT:
2261             ddwwStr = "DONT";
2262             break;
2263           case TN_WILL:
2264             ddwwStr = "WILL";
2265             break;
2266           case TN_WONT:
2267             ddwwStr = "WONT";
2268             break;
2269           default:
2270             ddwwStr = buf1;
2271             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2272             break;
2273         }
2274         switch (option) {
2275           case TN_ECHO:
2276             optionStr = "ECHO";
2277             break;
2278           default:
2279             optionStr = buf2;
2280             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2281             break;
2282         }
2283         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2284     }
2285     msg[0] = TN_IAC;
2286     msg[1] = ddww;
2287     msg[2] = option;
2288     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2289     if (outCount < 3) {
2290         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2291     }
2292 }
2293
2294 void
2295 DoEcho()
2296 {
2297     if (!appData.icsActive) return;
2298     TelnetRequest(TN_DO, TN_ECHO);
2299 }
2300
2301 void
2302 DontEcho()
2303 {
2304     if (!appData.icsActive) return;
2305     TelnetRequest(TN_DONT, TN_ECHO);
2306 }
2307
2308 void
2309 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2310 {
2311     /* put the holdings sent to us by the server on the board holdings area */
2312     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2313     char p;
2314     ChessSquare piece;
2315
2316     if(gameInfo.holdingsWidth < 2)  return;
2317     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2318         return; // prevent overwriting by pre-board holdings
2319
2320     if( (int)lowestPiece >= BlackPawn ) {
2321         holdingsColumn = 0;
2322         countsColumn = 1;
2323         holdingsStartRow = BOARD_HEIGHT-1;
2324         direction = -1;
2325     } else {
2326         holdingsColumn = BOARD_WIDTH-1;
2327         countsColumn = BOARD_WIDTH-2;
2328         holdingsStartRow = 0;
2329         direction = 1;
2330     }
2331
2332     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2333         board[i][holdingsColumn] = EmptySquare;
2334         board[i][countsColumn]   = (ChessSquare) 0;
2335     }
2336     while( (p=*holdings++) != NULLCHAR ) {
2337         piece = CharToPiece( ToUpper(p) );
2338         if(piece == EmptySquare) continue;
2339         /*j = (int) piece - (int) WhitePawn;*/
2340         j = PieceToNumber(piece);
2341         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2342         if(j < 0) continue;               /* should not happen */
2343         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2344         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2345         board[holdingsStartRow+j*direction][countsColumn]++;
2346     }
2347 }
2348
2349
2350 void
2351 VariantSwitch(Board board, VariantClass newVariant)
2352 {
2353    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2354    static Board oldBoard;
2355
2356    startedFromPositionFile = FALSE;
2357    if(gameInfo.variant == newVariant) return;
2358
2359    /* [HGM] This routine is called each time an assignment is made to
2360     * gameInfo.variant during a game, to make sure the board sizes
2361     * are set to match the new variant. If that means adding or deleting
2362     * holdings, we shift the playing board accordingly
2363     * This kludge is needed because in ICS observe mode, we get boards
2364     * of an ongoing game without knowing the variant, and learn about the
2365     * latter only later. This can be because of the move list we requested,
2366     * in which case the game history is refilled from the beginning anyway,
2367     * but also when receiving holdings of a crazyhouse game. In the latter
2368     * case we want to add those holdings to the already received position.
2369     */
2370
2371
2372    if (appData.debugMode) {
2373      fprintf(debugFP, "Switch board from %s to %s\n",
2374              VariantName(gameInfo.variant), VariantName(newVariant));
2375      setbuf(debugFP, NULL);
2376    }
2377    shuffleOpenings = 0;       /* [HGM] shuffle */
2378    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2379    switch(newVariant)
2380      {
2381      case VariantShogi:
2382        newWidth = 9;  newHeight = 9;
2383        gameInfo.holdingsSize = 7;
2384      case VariantBughouse:
2385      case VariantCrazyhouse:
2386        newHoldingsWidth = 2; break;
2387      case VariantGreat:
2388        newWidth = 10;
2389      case VariantSuper:
2390        newHoldingsWidth = 2;
2391        gameInfo.holdingsSize = 8;
2392        break;
2393      case VariantGothic:
2394      case VariantCapablanca:
2395      case VariantCapaRandom:
2396        newWidth = 10;
2397      default:
2398        newHoldingsWidth = gameInfo.holdingsSize = 0;
2399      };
2400
2401    if(newWidth  != gameInfo.boardWidth  ||
2402       newHeight != gameInfo.boardHeight ||
2403       newHoldingsWidth != gameInfo.holdingsWidth ) {
2404
2405      /* shift position to new playing area, if needed */
2406      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2407        for(i=0; i<BOARD_HEIGHT; i++)
2408          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2409            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2410              board[i][j];
2411        for(i=0; i<newHeight; i++) {
2412          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2413          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2414        }
2415      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2416        for(i=0; i<BOARD_HEIGHT; i++)
2417          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2418            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419              board[i][j];
2420      }
2421      gameInfo.boardWidth  = newWidth;
2422      gameInfo.boardHeight = newHeight;
2423      gameInfo.holdingsWidth = newHoldingsWidth;
2424      gameInfo.variant = newVariant;
2425      InitDrawingSizes(-2, 0);
2426    } else gameInfo.variant = newVariant;
2427    CopyBoard(oldBoard, board);   // remember correctly formatted board
2428      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2429    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2430 }
2431
2432 static int loggedOn = FALSE;
2433
2434 /*-- Game start info cache: --*/
2435 int gs_gamenum;
2436 char gs_kind[MSG_SIZ];
2437 static char player1Name[128] = "";
2438 static char player2Name[128] = "";
2439 static char cont_seq[] = "\n\\   ";
2440 static int player1Rating = -1;
2441 static int player2Rating = -1;
2442 /*----------------------------*/
2443
2444 ColorClass curColor = ColorNormal;
2445 int suppressKibitz = 0;
2446
2447 // [HGM] seekgraph
2448 Boolean soughtPending = FALSE;
2449 Boolean seekGraphUp;
2450 #define MAX_SEEK_ADS 200
2451 #define SQUARE 0x80
2452 char *seekAdList[MAX_SEEK_ADS];
2453 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2454 float tcList[MAX_SEEK_ADS];
2455 char colorList[MAX_SEEK_ADS];
2456 int nrOfSeekAds = 0;
2457 int minRating = 1010, maxRating = 2800;
2458 int hMargin = 10, vMargin = 20, h, w;
2459 extern int squareSize, lineGap;
2460
2461 void
2462 PlotSeekAd(int i)
2463 {
2464         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2465         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2466         if(r < minRating+100 && r >=0 ) r = minRating+100;
2467         if(r > maxRating) r = maxRating;
2468         if(tc < 1.) tc = 1.;
2469         if(tc > 95.) tc = 95.;
2470         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2471         y = ((double)r - minRating)/(maxRating - minRating)
2472             * (h-vMargin-squareSize/8-1) + vMargin;
2473         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2474         if(strstr(seekAdList[i], " u ")) color = 1;
2475         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2476            !strstr(seekAdList[i], "bullet") &&
2477            !strstr(seekAdList[i], "blitz") &&
2478            !strstr(seekAdList[i], "standard") ) color = 2;
2479         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2480         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2481 }
2482
2483 void
2484 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2485 {
2486         char buf[MSG_SIZ], *ext = "";
2487         VariantClass v = StringToVariant(type);
2488         if(strstr(type, "wild")) {
2489             ext = type + 4; // append wild number
2490             if(v == VariantFischeRandom) type = "chess960"; else
2491             if(v == VariantLoadable) type = "setup"; else
2492             type = VariantName(v);
2493         }
2494         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2495         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2496             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2497             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2498             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2499             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2500             seekNrList[nrOfSeekAds] = nr;
2501             zList[nrOfSeekAds] = 0;
2502             seekAdList[nrOfSeekAds++] = StrSave(buf);
2503             if(plot) PlotSeekAd(nrOfSeekAds-1);
2504         }
2505 }
2506
2507 void
2508 EraseSeekDot(int i)
2509 {
2510     int x = xList[i], y = yList[i], d=squareSize/4, k;
2511     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2512     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2513     // now replot every dot that overlapped
2514     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2515         int xx = xList[k], yy = yList[k];
2516         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2517             DrawSeekDot(xx, yy, colorList[k]);
2518     }
2519 }
2520
2521 void
2522 RemoveSeekAd(int nr)
2523 {
2524         int i;
2525         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2526             EraseSeekDot(i);
2527             if(seekAdList[i]) free(seekAdList[i]);
2528             seekAdList[i] = seekAdList[--nrOfSeekAds];
2529             seekNrList[i] = seekNrList[nrOfSeekAds];
2530             ratingList[i] = ratingList[nrOfSeekAds];
2531             colorList[i]  = colorList[nrOfSeekAds];
2532             tcList[i] = tcList[nrOfSeekAds];
2533             xList[i]  = xList[nrOfSeekAds];
2534             yList[i]  = yList[nrOfSeekAds];
2535             zList[i]  = zList[nrOfSeekAds];
2536             seekAdList[nrOfSeekAds] = NULL;
2537             break;
2538         }
2539 }
2540
2541 Boolean
2542 MatchSoughtLine(char *line)
2543 {
2544     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2545     int nr, base, inc, u=0; char dummy;
2546
2547     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2548        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2549        (u=1) &&
2550        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2551         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2552         // match: compact and save the line
2553         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2554         return TRUE;
2555     }
2556     return FALSE;
2557 }
2558
2559 int
2560 DrawSeekGraph()
2561 {
2562     int i;
2563     if(!seekGraphUp) return FALSE;
2564     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2565     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2566
2567     DrawSeekBackground(0, 0, w, h);
2568     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2569     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2570     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2571         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2572         yy = h-1-yy;
2573         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2574         if(i%500 == 0) {
2575             char buf[MSG_SIZ];
2576             snprintf(buf, MSG_SIZ, "%d", i);
2577             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2578         }
2579     }
2580     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2581     for(i=1; i<100; i+=(i<10?1:5)) {
2582         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2583         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2584         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2585             char buf[MSG_SIZ];
2586             snprintf(buf, MSG_SIZ, "%d", i);
2587             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2588         }
2589     }
2590     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2591     return TRUE;
2592 }
2593
2594 int SeekGraphClick(ClickType click, int x, int y, int moving)
2595 {
2596     static int lastDown = 0, displayed = 0, lastSecond;
2597     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2598         if(click == Release || moving) return FALSE;
2599         nrOfSeekAds = 0;
2600         soughtPending = TRUE;
2601         SendToICS(ics_prefix);
2602         SendToICS("sought\n"); // should this be "sought all"?
2603     } else { // issue challenge based on clicked ad
2604         int dist = 10000; int i, closest = 0, second = 0;
2605         for(i=0; i<nrOfSeekAds; i++) {
2606             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2607             if(d < dist) { dist = d; closest = i; }
2608             second += (d - zList[i] < 120); // count in-range ads
2609             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2610         }
2611         if(dist < 120) {
2612             char buf[MSG_SIZ];
2613             second = (second > 1);
2614             if(displayed != closest || second != lastSecond) {
2615                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2616                 lastSecond = second; displayed = closest;
2617             }
2618             if(click == Press) {
2619                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2620                 lastDown = closest;
2621                 return TRUE;
2622             } // on press 'hit', only show info
2623             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2624             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2625             SendToICS(ics_prefix);
2626             SendToICS(buf);
2627             return TRUE; // let incoming board of started game pop down the graph
2628         } else if(click == Release) { // release 'miss' is ignored
2629             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2630             if(moving == 2) { // right up-click
2631                 nrOfSeekAds = 0; // refresh graph
2632                 soughtPending = TRUE;
2633                 SendToICS(ics_prefix);
2634                 SendToICS("sought\n"); // should this be "sought all"?
2635             }
2636             return TRUE;
2637         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2638         // press miss or release hit 'pop down' seek graph
2639         seekGraphUp = FALSE;
2640         DrawPosition(TRUE, NULL);
2641     }
2642     return TRUE;
2643 }
2644
2645 void
2646 read_from_ics(isr, closure, data, count, error)
2647      InputSourceRef isr;
2648      VOIDSTAR closure;
2649      char *data;
2650      int count;
2651      int error;
2652 {
2653 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2654 #define STARTED_NONE 0
2655 #define STARTED_MOVES 1
2656 #define STARTED_BOARD 2
2657 #define STARTED_OBSERVE 3
2658 #define STARTED_HOLDINGS 4
2659 #define STARTED_CHATTER 5
2660 #define STARTED_COMMENT 6
2661 #define STARTED_MOVES_NOHIDE 7
2662
2663     static int started = STARTED_NONE;
2664     static char parse[20000];
2665     static int parse_pos = 0;
2666     static char buf[BUF_SIZE + 1];
2667     static int firstTime = TRUE, intfSet = FALSE;
2668     static ColorClass prevColor = ColorNormal;
2669     static int savingComment = FALSE;
2670     static int cmatch = 0; // continuation sequence match
2671     char *bp;
2672     char str[MSG_SIZ];
2673     int i, oldi;
2674     int buf_len;
2675     int next_out;
2676     int tkind;
2677     int backup;    /* [DM] For zippy color lines */
2678     char *p;
2679     char talker[MSG_SIZ]; // [HGM] chat
2680     int channel;
2681
2682     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2683
2684     if (appData.debugMode) {
2685       if (!error) {
2686         fprintf(debugFP, "<ICS: ");
2687         show_bytes(debugFP, data, count);
2688         fprintf(debugFP, "\n");
2689       }
2690     }
2691
2692     if (appData.debugMode) { int f = forwardMostMove;
2693         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2694                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2695                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2696     }
2697     if (count > 0) {
2698         /* If last read ended with a partial line that we couldn't parse,
2699            prepend it to the new read and try again. */
2700         if (leftover_len > 0) {
2701             for (i=0; i<leftover_len; i++)
2702               buf[i] = buf[leftover_start + i];
2703         }
2704
2705     /* copy new characters into the buffer */
2706     bp = buf + leftover_len;
2707     buf_len=leftover_len;
2708     for (i=0; i<count; i++)
2709     {
2710         // ignore these
2711         if (data[i] == '\r')
2712             continue;
2713
2714         // join lines split by ICS?
2715         if (!appData.noJoin)
2716         {
2717             /*
2718                 Joining just consists of finding matches against the
2719                 continuation sequence, and discarding that sequence
2720                 if found instead of copying it.  So, until a match
2721                 fails, there's nothing to do since it might be the
2722                 complete sequence, and thus, something we don't want
2723                 copied.
2724             */
2725             if (data[i] == cont_seq[cmatch])
2726             {
2727                 cmatch++;
2728                 if (cmatch == strlen(cont_seq))
2729                 {
2730                     cmatch = 0; // complete match.  just reset the counter
2731
2732                     /*
2733                         it's possible for the ICS to not include the space
2734                         at the end of the last word, making our [correct]
2735                         join operation fuse two separate words.  the server
2736                         does this when the space occurs at the width setting.
2737                     */
2738                     if (!buf_len || buf[buf_len-1] != ' ')
2739                     {
2740                         *bp++ = ' ';
2741                         buf_len++;
2742                     }
2743                 }
2744                 continue;
2745             }
2746             else if (cmatch)
2747             {
2748                 /*
2749                     match failed, so we have to copy what matched before
2750                     falling through and copying this character.  In reality,
2751                     this will only ever be just the newline character, but
2752                     it doesn't hurt to be precise.
2753                 */
2754                 strncpy(bp, cont_seq, cmatch);
2755                 bp += cmatch;
2756                 buf_len += cmatch;
2757                 cmatch = 0;
2758             }
2759         }
2760
2761         // copy this char
2762         *bp++ = data[i];
2763         buf_len++;
2764     }
2765
2766         buf[buf_len] = NULLCHAR;
2767 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2768         next_out = 0;
2769         leftover_start = 0;
2770
2771         i = 0;
2772         while (i < buf_len) {
2773             /* Deal with part of the TELNET option negotiation
2774                protocol.  We refuse to do anything beyond the
2775                defaults, except that we allow the WILL ECHO option,
2776                which ICS uses to turn off password echoing when we are
2777                directly connected to it.  We reject this option
2778                if localLineEditing mode is on (always on in xboard)
2779                and we are talking to port 23, which might be a real
2780                telnet server that will try to keep WILL ECHO on permanently.
2781              */
2782             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2783                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2784                 unsigned char option;
2785                 oldi = i;
2786                 switch ((unsigned char) buf[++i]) {
2787                   case TN_WILL:
2788                     if (appData.debugMode)
2789                       fprintf(debugFP, "\n<WILL ");
2790                     switch (option = (unsigned char) buf[++i]) {
2791                       case TN_ECHO:
2792                         if (appData.debugMode)
2793                           fprintf(debugFP, "ECHO ");
2794                         /* Reply only if this is a change, according
2795                            to the protocol rules. */
2796                         if (remoteEchoOption) break;
2797                         if (appData.localLineEditing &&
2798                             atoi(appData.icsPort) == TN_PORT) {
2799                             TelnetRequest(TN_DONT, TN_ECHO);
2800                         } else {
2801                             EchoOff();
2802                             TelnetRequest(TN_DO, TN_ECHO);
2803                             remoteEchoOption = TRUE;
2804                         }
2805                         break;
2806                       default:
2807                         if (appData.debugMode)
2808                           fprintf(debugFP, "%d ", option);
2809                         /* Whatever this is, we don't want it. */
2810                         TelnetRequest(TN_DONT, option);
2811                         break;
2812                     }
2813                     break;
2814                   case TN_WONT:
2815                     if (appData.debugMode)
2816                       fprintf(debugFP, "\n<WONT ");
2817                     switch (option = (unsigned char) buf[++i]) {
2818                       case TN_ECHO:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "ECHO ");
2821                         /* Reply only if this is a change, according
2822                            to the protocol rules. */
2823                         if (!remoteEchoOption) break;
2824                         EchoOn();
2825                         TelnetRequest(TN_DONT, TN_ECHO);
2826                         remoteEchoOption = FALSE;
2827                         break;
2828                       default:
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "%d ", (unsigned char) option);
2831                         /* Whatever this is, it must already be turned
2832                            off, because we never agree to turn on
2833                            anything non-default, so according to the
2834                            protocol rules, we don't reply. */
2835                         break;
2836                     }
2837                     break;
2838                   case TN_DO:
2839                     if (appData.debugMode)
2840                       fprintf(debugFP, "\n<DO ");
2841                     switch (option = (unsigned char) buf[++i]) {
2842                       default:
2843                         /* Whatever this is, we refuse to do it. */
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", option);
2846                         TelnetRequest(TN_WONT, option);
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DONT:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DONT ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         /* Whatever this is, we are already not doing
2858                            it, because we never agree to do anything
2859                            non-default, so according to the protocol
2860                            rules, we don't reply. */
2861                         break;
2862                     }
2863                     break;
2864                   case TN_IAC:
2865                     if (appData.debugMode)
2866                       fprintf(debugFP, "\n<IAC ");
2867                     /* Doubled IAC; pass it through */
2868                     i--;
2869                     break;
2870                   default:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2873                     /* Drop all other telnet commands on the floor */
2874                     break;
2875                 }
2876                 if (oldi > next_out)
2877                   SendToPlayer(&buf[next_out], oldi - next_out);
2878                 if (++i > next_out)
2879                   next_out = i;
2880                 continue;
2881             }
2882
2883             /* OK, this at least will *usually* work */
2884             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2885                 loggedOn = TRUE;
2886             }
2887
2888             if (loggedOn && !intfSet) {
2889                 if (ics_type == ICS_ICC) {
2890                   snprintf(str, MSG_SIZ,
2891                           "/set-quietly interface %s\n/set-quietly style 12\n",
2892                           programVersion);
2893                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2894                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2895                 } else if (ics_type == ICS_CHESSNET) {
2896                   snprintf(str, MSG_SIZ, "/style 12\n");
2897                 } else {
2898                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2899                   strcat(str, programVersion);
2900                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2901                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2902                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2903 #ifdef WIN32
2904                   strcat(str, "$iset nohighlight 1\n");
2905 #endif
2906                   strcat(str, "$iset lock 1\n$style 12\n");
2907                 }
2908                 SendToICS(str);
2909                 NotifyFrontendLogin();
2910                 intfSet = TRUE;
2911             }
2912
2913             if (started == STARTED_COMMENT) {
2914                 /* Accumulate characters in comment */
2915                 parse[parse_pos++] = buf[i];
2916                 if (buf[i] == '\n') {
2917                     parse[parse_pos] = NULLCHAR;
2918                     if(chattingPartner>=0) {
2919                         char mess[MSG_SIZ];
2920                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2921                         OutputChatMessage(chattingPartner, mess);
2922                         chattingPartner = -1;
2923                         next_out = i+1; // [HGM] suppress printing in ICS window
2924                     } else
2925                     if(!suppressKibitz) // [HGM] kibitz
2926                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2927                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2928                         int nrDigit = 0, nrAlph = 0, j;
2929                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2930                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2931                         parse[parse_pos] = NULLCHAR;
2932                         // try to be smart: if it does not look like search info, it should go to
2933                         // ICS interaction window after all, not to engine-output window.
2934                         for(j=0; j<parse_pos; j++) { // count letters and digits
2935                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2936                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2937                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2938                         }
2939                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2940                             int depth=0; float score;
2941                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2942                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2943                                 pvInfoList[forwardMostMove-1].depth = depth;
2944                                 pvInfoList[forwardMostMove-1].score = 100*score;
2945                             }
2946                             OutputKibitz(suppressKibitz, parse);
2947                         } else {
2948                             char tmp[MSG_SIZ];
2949                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2950                             SendToPlayer(tmp, strlen(tmp));
2951                         }
2952                         next_out = i+1; // [HGM] suppress printing in ICS window
2953                     }
2954                     started = STARTED_NONE;
2955                 } else {
2956                     /* Don't match patterns against characters in comment */
2957                     i++;
2958                     continue;
2959                 }
2960             }
2961             if (started == STARTED_CHATTER) {
2962                 if (buf[i] != '\n') {
2963                     /* Don't match patterns against characters in chatter */
2964                     i++;
2965                     continue;
2966                 }
2967                 started = STARTED_NONE;
2968                 if(suppressKibitz) next_out = i+1;
2969             }
2970
2971             /* Kludge to deal with rcmd protocol */
2972             if (firstTime && looking_at(buf, &i, "\001*")) {
2973                 DisplayFatalError(&buf[1], 0, 1);
2974                 continue;
2975             } else {
2976                 firstTime = FALSE;
2977             }
2978
2979             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2980                 ics_type = ICS_ICC;
2981                 ics_prefix = "/";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2987                 ics_type = ICS_FICS;
2988                 ics_prefix = "$";
2989                 if (appData.debugMode)
2990                   fprintf(debugFP, "ics_type %d\n", ics_type);
2991                 continue;
2992             }
2993             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2994                 ics_type = ICS_CHESSNET;
2995                 ics_prefix = "/";
2996                 if (appData.debugMode)
2997                   fprintf(debugFP, "ics_type %d\n", ics_type);
2998                 continue;
2999             }
3000
3001             if (!loggedOn &&
3002                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3003                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3004                  looking_at(buf, &i, "will be \"*\""))) {
3005               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3006               continue;
3007             }
3008
3009             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3010               char buf[MSG_SIZ];
3011               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3012               DisplayIcsInteractionTitle(buf);
3013               have_set_title = TRUE;
3014             }
3015
3016             /* skip finger notes */
3017             if (started == STARTED_NONE &&
3018                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3019                  (buf[i] == '1' && buf[i+1] == '0')) &&
3020                 buf[i+2] == ':' && buf[i+3] == ' ') {
3021               started = STARTED_CHATTER;
3022               i += 3;
3023               continue;
3024             }
3025
3026             oldi = i;
3027             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3028             if(appData.seekGraph) {
3029                 if(soughtPending && MatchSoughtLine(buf+i)) {
3030                     i = strstr(buf+i, "rated") - buf;
3031                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3032                     next_out = leftover_start = i;
3033                     started = STARTED_CHATTER;
3034                     suppressKibitz = TRUE;
3035                     continue;
3036                 }
3037                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3038                         && looking_at(buf, &i, "* ads displayed")) {
3039                     soughtPending = FALSE;
3040                     seekGraphUp = TRUE;
3041                     DrawSeekGraph();
3042                     continue;
3043                 }
3044                 if(appData.autoRefresh) {
3045                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3046                         int s = (ics_type == ICS_ICC); // ICC format differs
3047                         if(seekGraphUp)
3048                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3049                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3050                         looking_at(buf, &i, "*% "); // eat prompt
3051                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3052                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                         next_out = i; // suppress
3054                         continue;
3055                     }
3056                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3057                         char *p = star_match[0];
3058                         while(*p) {
3059                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3060                             while(*p && *p++ != ' '); // next
3061                         }
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3064                         next_out = i;
3065                         continue;
3066                     }
3067                 }
3068             }
3069
3070             /* skip formula vars */
3071             if (started == STARTED_NONE &&
3072                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3073               started = STARTED_CHATTER;
3074               i += 3;
3075               continue;
3076             }
3077
3078             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3079             if (appData.autoKibitz && started == STARTED_NONE &&
3080                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3081                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3082                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3083                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3084                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3085                         suppressKibitz = TRUE;
3086                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3087                         next_out = i;
3088                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3089                                 && (gameMode == IcsPlayingWhite)) ||
3090                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3091                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3092                             started = STARTED_CHATTER; // own kibitz we simply discard
3093                         else {
3094                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3095                             parse_pos = 0; parse[0] = NULLCHAR;
3096                             savingComment = TRUE;
3097                             suppressKibitz = gameMode != IcsObserving ? 2 :
3098                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3099                         }
3100                         continue;
3101                 } else
3102                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3103                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3104                          && atoi(star_match[0])) {
3105                     // suppress the acknowledgements of our own autoKibitz
3106                     char *p;
3107                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3109                     SendToPlayer(star_match[0], strlen(star_match[0]));
3110                     if(looking_at(buf, &i, "*% ")) // eat prompt
3111                         suppressKibitz = FALSE;
3112                     next_out = i;
3113                     continue;
3114                 }
3115             } // [HGM] kibitz: end of patch
3116
3117             // [HGM] chat: intercept tells by users for which we have an open chat window
3118             channel = -1;
3119             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3120                                            looking_at(buf, &i, "* whispers:") ||
3121                                            looking_at(buf, &i, "* kibitzes:") ||
3122                                            looking_at(buf, &i, "* shouts:") ||
3123                                            looking_at(buf, &i, "* c-shouts:") ||
3124                                            looking_at(buf, &i, "--> * ") ||
3125                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3126                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3127                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3128                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3129                 int p;
3130                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3131                 chattingPartner = -1;
3132
3133                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3134                 for(p=0; p<MAX_CHAT; p++) {
3135                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3136                     talker[0] = '['; strcat(talker, "] ");
3137                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3138                     chattingPartner = p; break;
3139                     }
3140                 } else
3141                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3142                 for(p=0; p<MAX_CHAT; p++) {
3143                     if(!strcmp("kibitzes", chatPartner[p])) {
3144                         talker[0] = '['; strcat(talker, "] ");
3145                         chattingPartner = p; break;
3146                     }
3147                 } else
3148                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3149                 for(p=0; p<MAX_CHAT; p++) {
3150                     if(!strcmp("whispers", chatPartner[p])) {
3151                         talker[0] = '['; strcat(talker, "] ");
3152                         chattingPartner = p; break;
3153                     }
3154                 } else
3155                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3156                   if(buf[i-8] == '-' && buf[i-3] == 't')
3157                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3158                     if(!strcmp("c-shouts", chatPartner[p])) {
3159                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3160                         chattingPartner = p; break;
3161                     }
3162                   }
3163                   if(chattingPartner < 0)
3164                   for(p=0; p<MAX_CHAT; p++) {
3165                     if(!strcmp("shouts", chatPartner[p])) {
3166                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3167                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3168                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3169                         chattingPartner = p; break;
3170                     }
3171                   }
3172                 }
3173                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3174                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3175                     talker[0] = 0; Colorize(ColorTell, FALSE);
3176                     chattingPartner = p; break;
3177                 }
3178                 if(chattingPartner<0) i = oldi; else {
3179                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3180                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3181                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3182                     started = STARTED_COMMENT;
3183                     parse_pos = 0; parse[0] = NULLCHAR;
3184                     savingComment = 3 + chattingPartner; // counts as TRUE
3185                     suppressKibitz = TRUE;
3186                     continue;
3187                 }
3188             } // [HGM] chat: end of patch
3189
3190           backup = i;
3191             if (appData.zippyTalk || appData.zippyPlay) {
3192                 /* [DM] Backup address for color zippy lines */
3193 #if ZIPPY
3194                if (loggedOn == TRUE)
3195                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3196                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3197 #endif
3198             } // [DM] 'else { ' deleted
3199                 if (
3200                     /* Regular tells and says */
3201                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3202                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3203                     looking_at(buf, &i, "* says: ") ||
3204                     /* Don't color "message" or "messages" output */
3205                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3206                     looking_at(buf, &i, "*. * at *:*: ") ||
3207                     looking_at(buf, &i, "--* (*:*): ") ||
3208                     /* Message notifications (same color as tells) */
3209                     looking_at(buf, &i, "* has left a message ") ||
3210                     looking_at(buf, &i, "* just sent you a message:\n") ||
3211                     /* Whispers and kibitzes */
3212                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3213                     looking_at(buf, &i, "* kibitzes: ") ||
3214                     /* Channel tells */
3215                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3216
3217                   if (tkind == 1 && strchr(star_match[0], ':')) {
3218                       /* Avoid "tells you:" spoofs in channels */
3219                      tkind = 3;
3220                   }
3221                   if (star_match[0][0] == NULLCHAR ||
3222                       strchr(star_match[0], ' ') ||
3223                       (tkind == 3 && strchr(star_match[1], ' '))) {
3224                     /* Reject bogus matches */
3225                     i = oldi;
3226                   } else {
3227                     if (appData.colorize) {
3228                       if (oldi > next_out) {
3229                         SendToPlayer(&buf[next_out], oldi - next_out);
3230                         next_out = oldi;
3231                       }
3232                       switch (tkind) {
3233                       case 1:
3234                         Colorize(ColorTell, FALSE);
3235                         curColor = ColorTell;
3236                         break;
3237                       case 2:
3238                         Colorize(ColorKibitz, FALSE);
3239                         curColor = ColorKibitz;
3240                         break;
3241                       case 3:
3242                         p = strrchr(star_match[1], '(');
3243                         if (p == NULL) {
3244                           p = star_match[1];
3245                         } else {
3246                           p++;
3247                         }
3248                         if (atoi(p) == 1) {
3249                           Colorize(ColorChannel1, FALSE);
3250                           curColor = ColorChannel1;
3251                         } else {
3252                           Colorize(ColorChannel, FALSE);
3253                           curColor = ColorChannel;
3254                         }
3255                         break;
3256                       case 5:
3257                         curColor = ColorNormal;
3258                         break;
3259                       }
3260                     }
3261                     if (started == STARTED_NONE && appData.autoComment &&
3262                         (gameMode == IcsObserving ||
3263                          gameMode == IcsPlayingWhite ||
3264                          gameMode == IcsPlayingBlack)) {
3265                       parse_pos = i - oldi;
3266                       memcpy(parse, &buf[oldi], parse_pos);
3267                       parse[parse_pos] = NULLCHAR;
3268                       started = STARTED_COMMENT;
3269                       savingComment = TRUE;
3270                     } else {
3271                       started = STARTED_CHATTER;
3272                       savingComment = FALSE;
3273                     }
3274                     loggedOn = TRUE;
3275                     continue;
3276                   }
3277                 }
3278
3279                 if (looking_at(buf, &i, "* s-shouts: ") ||
3280                     looking_at(buf, &i, "* c-shouts: ")) {
3281                     if (appData.colorize) {
3282                         if (oldi > next_out) {
3283                             SendToPlayer(&buf[next_out], oldi - next_out);
3284                             next_out = oldi;
3285                         }
3286                         Colorize(ColorSShout, FALSE);
3287                         curColor = ColorSShout;
3288                     }
3289                     loggedOn = TRUE;
3290                     started = STARTED_CHATTER;
3291                     continue;
3292                 }
3293
3294                 if (looking_at(buf, &i, "--->")) {
3295                     loggedOn = TRUE;
3296                     continue;
3297                 }
3298
3299                 if (looking_at(buf, &i, "* shouts: ") ||
3300                     looking_at(buf, &i, "--> ")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorShout, FALSE);
3307                         curColor = ColorShout;
3308                     }
3309                     loggedOn = TRUE;
3310                     started = STARTED_CHATTER;
3311                     continue;
3312                 }
3313
3314                 if (looking_at( buf, &i, "Challenge:")) {
3315                     if (appData.colorize) {
3316                         if (oldi > next_out) {
3317                             SendToPlayer(&buf[next_out], oldi - next_out);
3318                             next_out = oldi;
3319                         }
3320                         Colorize(ColorChallenge, FALSE);
3321                         curColor = ColorChallenge;
3322                     }
3323                     loggedOn = TRUE;
3324                     continue;
3325                 }
3326
3327                 if (looking_at(buf, &i, "* offers you") ||
3328                     looking_at(buf, &i, "* offers to be") ||
3329                     looking_at(buf, &i, "* would like to") ||
3330                     looking_at(buf, &i, "* requests to") ||
3331                     looking_at(buf, &i, "Your opponent offers") ||
3332                     looking_at(buf, &i, "Your opponent requests")) {
3333
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorRequest, FALSE);
3340                         curColor = ColorRequest;
3341                     }
3342                     continue;
3343                 }
3344
3345                 if (looking_at(buf, &i, "* (*) seeking")) {
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorSeek, FALSE);
3352                         curColor = ColorSeek;
3353                     }
3354                     continue;
3355             }
3356
3357           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3358
3359             if (looking_at(buf, &i, "\\   ")) {
3360                 if (prevColor != ColorNormal) {
3361                     if (oldi > next_out) {
3362                         SendToPlayer(&buf[next_out], oldi - next_out);
3363                         next_out = oldi;
3364                     }
3365                     Colorize(prevColor, TRUE);
3366                     curColor = prevColor;
3367                 }
3368                 if (savingComment) {
3369                     parse_pos = i - oldi;
3370                     memcpy(parse, &buf[oldi], parse_pos);
3371                     parse[parse_pos] = NULLCHAR;
3372                     started = STARTED_COMMENT;
3373                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3374                         chattingPartner = savingComment - 3; // kludge to remember the box
3375                 } else {
3376                     started = STARTED_CHATTER;
3377                 }
3378                 continue;
3379             }
3380
3381             if (looking_at(buf, &i, "Black Strength :") ||
3382                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3383                 looking_at(buf, &i, "<10>") ||
3384                 looking_at(buf, &i, "#@#")) {
3385                 /* Wrong board style */
3386                 loggedOn = TRUE;
3387                 SendToICS(ics_prefix);
3388                 SendToICS("set style 12\n");
3389                 SendToICS(ics_prefix);
3390                 SendToICS("refresh\n");
3391                 continue;
3392             }
3393
3394             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3395                 ICSInitScript();
3396                 have_sent_ICS_logon = 1;
3397                 continue;
3398             }
3399
3400             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3401                 (looking_at(buf, &i, "\n<12> ") ||
3402                  looking_at(buf, &i, "<12> "))) {
3403                 loggedOn = TRUE;
3404                 if (oldi > next_out) {
3405                     SendToPlayer(&buf[next_out], oldi - next_out);
3406                 }
3407                 next_out = i;
3408                 started = STARTED_BOARD;
3409                 parse_pos = 0;
3410                 continue;
3411             }
3412
3413             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3414                 looking_at(buf, &i, "<b1> ")) {
3415                 if (oldi > next_out) {
3416                     SendToPlayer(&buf[next_out], oldi - next_out);
3417                 }
3418                 next_out = i;
3419                 started = STARTED_HOLDINGS;
3420                 parse_pos = 0;
3421                 continue;
3422             }
3423
3424             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3425                 loggedOn = TRUE;
3426                 /* Header for a move list -- first line */
3427
3428                 switch (ics_getting_history) {
3429                   case H_FALSE:
3430                     switch (gameMode) {
3431                       case IcsIdle:
3432                       case BeginningOfGame:
3433                         /* User typed "moves" or "oldmoves" while we
3434                            were idle.  Pretend we asked for these
3435                            moves and soak them up so user can step
3436                            through them and/or save them.
3437                            */
3438                         Reset(FALSE, TRUE);
3439                         gameMode = IcsObserving;
3440                         ModeHighlight();
3441                         ics_gamenum = -1;
3442                         ics_getting_history = H_GOT_UNREQ_HEADER;
3443                         break;
3444                       case EditGame: /*?*/
3445                       case EditPosition: /*?*/
3446                         /* Should above feature work in these modes too? */
3447                         /* For now it doesn't */
3448                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3449                         break;
3450                       default:
3451                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3452                         break;
3453                     }
3454                     break;
3455                   case H_REQUESTED:
3456                     /* Is this the right one? */
3457                     if (gameInfo.white && gameInfo.black &&
3458                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3459                         strcmp(gameInfo.black, star_match[2]) == 0) {
3460                         /* All is well */
3461                         ics_getting_history = H_GOT_REQ_HEADER;
3462                     }
3463                     break;
3464                   case H_GOT_REQ_HEADER:
3465                   case H_GOT_UNREQ_HEADER:
3466                   case H_GOT_UNWANTED_HEADER:
3467                   case H_GETTING_MOVES:
3468                     /* Should not happen */
3469                     DisplayError(_("Error gathering move list: two headers"), 0);
3470                     ics_getting_history = H_FALSE;
3471                     break;
3472                 }
3473
3474                 /* Save player ratings into gameInfo if needed */
3475                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3476                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3477                     (gameInfo.whiteRating == -1 ||
3478                      gameInfo.blackRating == -1)) {
3479
3480                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3481                     gameInfo.blackRating = string_to_rating(star_match[3]);
3482                     if (appData.debugMode)
3483                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3484                               gameInfo.whiteRating, gameInfo.blackRating);
3485                 }
3486                 continue;
3487             }
3488
3489             if (looking_at(buf, &i,
3490               "* * match, initial time: * minute*, increment: * second")) {
3491                 /* Header for a move list -- second line */
3492                 /* Initial board will follow if this is a wild game */
3493                 if (gameInfo.event != NULL) free(gameInfo.event);
3494                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3495                 gameInfo.event = StrSave(str);
3496                 /* [HGM] we switched variant. Translate boards if needed. */
3497                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i, "Move  ")) {
3502                 /* Beginning of a move list */
3503                 switch (ics_getting_history) {
3504                   case H_FALSE:
3505                     /* Normally should not happen */
3506                     /* Maybe user hit reset while we were parsing */
3507                     break;
3508                   case H_REQUESTED:
3509                     /* Happens if we are ignoring a move list that is not
3510                      * the one we just requested.  Common if the user
3511                      * tries to observe two games without turning off
3512                      * getMoveList */
3513                     break;
3514                   case H_GETTING_MOVES:
3515                     /* Should not happen */
3516                     DisplayError(_("Error gathering move list: nested"), 0);
3517                     ics_getting_history = H_FALSE;
3518                     break;
3519                   case H_GOT_REQ_HEADER:
3520                     ics_getting_history = H_GETTING_MOVES;
3521                     started = STARTED_MOVES;
3522                     parse_pos = 0;
3523                     if (oldi > next_out) {
3524                         SendToPlayer(&buf[next_out], oldi - next_out);
3525                     }
3526                     break;
3527                   case H_GOT_UNREQ_HEADER:
3528                     ics_getting_history = H_GETTING_MOVES;
3529                     started = STARTED_MOVES_NOHIDE;
3530                     parse_pos = 0;
3531                     break;
3532                   case H_GOT_UNWANTED_HEADER:
3533                     ics_getting_history = H_FALSE;
3534                     break;
3535                 }
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "% ") ||
3540                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3541                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3542                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3543                     soughtPending = FALSE;
3544                     seekGraphUp = TRUE;
3545                     DrawSeekGraph();
3546                 }
3547                 if(suppressKibitz) next_out = i;
3548                 savingComment = FALSE;
3549                 suppressKibitz = 0;
3550                 switch (started) {
3551                   case STARTED_MOVES:
3552                   case STARTED_MOVES_NOHIDE:
3553                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3554                     parse[parse_pos + i - oldi] = NULLCHAR;
3555                     ParseGameHistory(parse);
3556 #if ZIPPY
3557                     if (appData.zippyPlay && first.initDone) {
3558                         FeedMovesToProgram(&first, forwardMostMove);
3559                         if (gameMode == IcsPlayingWhite) {
3560                             if (WhiteOnMove(forwardMostMove)) {
3561                                 if (first.sendTime) {
3562                                   if (first.useColors) {
3563                                     SendToProgram("black\n", &first);
3564                                   }
3565                                   SendTimeRemaining(&first, TRUE);
3566                                 }
3567                                 if (first.useColors) {
3568                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3569                                 }
3570                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3571                                 first.maybeThinking = TRUE;
3572                             } else {
3573                                 if (first.usePlayother) {
3574                                   if (first.sendTime) {
3575                                     SendTimeRemaining(&first, TRUE);
3576                                   }
3577                                   SendToProgram("playother\n", &first);
3578                                   firstMove = FALSE;
3579                                 } else {
3580                                   firstMove = TRUE;
3581                                 }
3582                             }
3583                         } else if (gameMode == IcsPlayingBlack) {
3584                             if (!WhiteOnMove(forwardMostMove)) {
3585                                 if (first.sendTime) {
3586                                   if (first.useColors) {
3587                                     SendToProgram("white\n", &first);
3588                                   }
3589                                   SendTimeRemaining(&first, FALSE);
3590                                 }
3591                                 if (first.useColors) {
3592                                   SendToProgram("black\n", &first);
3593                                 }
3594                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3595                                 first.maybeThinking = TRUE;
3596                             } else {
3597                                 if (first.usePlayother) {
3598                                   if (first.sendTime) {
3599                                     SendTimeRemaining(&first, FALSE);
3600                                   }
3601                                   SendToProgram("playother\n", &first);
3602                                   firstMove = FALSE;
3603                                 } else {
3604                                   firstMove = TRUE;
3605                                 }
3606                             }
3607                         }
3608                     }
3609 #endif
3610                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3611                         /* Moves came from oldmoves or moves command
3612                            while we weren't doing anything else.
3613                            */
3614                         currentMove = forwardMostMove;
3615                         ClearHighlights();/*!!could figure this out*/
3616                         flipView = appData.flipView;
3617                         DrawPosition(TRUE, boards[currentMove]);
3618                         DisplayBothClocks();
3619                         snprintf(str, MSG_SIZ, "%s vs. %s",
3620                                 gameInfo.white, gameInfo.black);
3621                         DisplayTitle(str);
3622                         gameMode = IcsIdle;
3623                     } else {
3624                         /* Moves were history of an active game */
3625                         if (gameInfo.resultDetails != NULL) {
3626                             free(gameInfo.resultDetails);
3627                             gameInfo.resultDetails = NULL;
3628                         }
3629                     }
3630                     HistorySet(parseList, backwardMostMove,
3631                                forwardMostMove, currentMove-1);
3632                     DisplayMove(currentMove - 1);
3633                     if (started == STARTED_MOVES) next_out = i;
3634                     started = STARTED_NONE;
3635                     ics_getting_history = H_FALSE;
3636                     break;
3637
3638                   case STARTED_OBSERVE:
3639                     started = STARTED_NONE;
3640                     SendToICS(ics_prefix);
3641                     SendToICS("refresh\n");
3642                     break;
3643
3644                   default:
3645                     break;
3646                 }
3647                 if(bookHit) { // [HGM] book: simulate book reply
3648                     static char bookMove[MSG_SIZ]; // a bit generous?
3649
3650                     programStats.nodes = programStats.depth = programStats.time =
3651                     programStats.score = programStats.got_only_move = 0;
3652                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3653
3654                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3655                     strcat(bookMove, bookHit);
3656                     HandleMachineMove(bookMove, &first);
3657                 }
3658                 continue;
3659             }
3660
3661             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3662                  started == STARTED_HOLDINGS ||
3663                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3664                 /* Accumulate characters in move list or board */
3665                 parse[parse_pos++] = buf[i];
3666             }
3667
3668             /* Start of game messages.  Mostly we detect start of game
3669                when the first board image arrives.  On some versions
3670                of the ICS, though, we need to do a "refresh" after starting
3671                to observe in order to get the current board right away. */
3672             if (looking_at(buf, &i, "Adding game * to observation list")) {
3673                 started = STARTED_OBSERVE;
3674                 continue;
3675             }
3676
3677             /* Handle auto-observe */
3678             if (appData.autoObserve &&
3679                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3680                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3681                 char *player;
3682                 /* Choose the player that was highlighted, if any. */
3683                 if (star_match[0][0] == '\033' ||
3684                     star_match[1][0] != '\033') {
3685                     player = star_match[0];
3686                 } else {
3687                     player = star_match[2];
3688                 }
3689                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3690                         ics_prefix, StripHighlightAndTitle(player));
3691                 SendToICS(str);
3692
3693                 /* Save ratings from notify string */
3694                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3695                 player1Rating = string_to_rating(star_match[1]);
3696                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3697                 player2Rating = string_to_rating(star_match[3]);
3698
3699                 if (appData.debugMode)
3700                   fprintf(debugFP,
3701                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3702                           player1Name, player1Rating,
3703                           player2Name, player2Rating);
3704
3705                 continue;
3706             }
3707
3708             /* Deal with automatic examine mode after a game,
3709                and with IcsObserving -> IcsExamining transition */
3710             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3711                 looking_at(buf, &i, "has made you an examiner of game *")) {
3712
3713                 int gamenum = atoi(star_match[0]);
3714                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3715                     gamenum == ics_gamenum) {
3716                     /* We were already playing or observing this game;
3717                        no need to refetch history */
3718                     gameMode = IcsExamining;
3719                     if (pausing) {
3720                         pauseExamForwardMostMove = forwardMostMove;
3721                     } else if (currentMove < forwardMostMove) {
3722                         ForwardInner(forwardMostMove);
3723                     }
3724                 } else {
3725                     /* I don't think this case really can happen */
3726                     SendToICS(ics_prefix);
3727                     SendToICS("refresh\n");
3728                 }
3729                 continue;
3730             }
3731
3732             /* Error messages */
3733 //          if (ics_user_moved) {
3734             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3735                 if (looking_at(buf, &i, "Illegal move") ||
3736                     looking_at(buf, &i, "Not a legal move") ||
3737                     looking_at(buf, &i, "Your king is in check") ||
3738                     looking_at(buf, &i, "It isn't your turn") ||
3739                     looking_at(buf, &i, "It is not your move")) {
3740                     /* Illegal move */
3741                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3742                         currentMove = forwardMostMove-1;
3743                         DisplayMove(currentMove - 1); /* before DMError */
3744                         DrawPosition(FALSE, boards[currentMove]);
3745                         SwitchClocks(forwardMostMove-1); // [HGM] race
3746                         DisplayBothClocks();
3747                     }
3748                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3749                     ics_user_moved = 0;
3750                     continue;
3751                 }
3752             }
3753
3754             if (looking_at(buf, &i, "still have time") ||
3755                 looking_at(buf, &i, "not out of time") ||
3756                 looking_at(buf, &i, "either player is out of time") ||
3757                 looking_at(buf, &i, "has timeseal; checking")) {
3758                 /* We must have called his flag a little too soon */
3759                 whiteFlag = blackFlag = FALSE;
3760                 continue;
3761             }
3762
3763             if (looking_at(buf, &i, "added * seconds to") ||
3764                 looking_at(buf, &i, "seconds were added to")) {
3765                 /* Update the clocks */
3766                 SendToICS(ics_prefix);
3767                 SendToICS("refresh\n");
3768                 continue;
3769             }
3770
3771             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3772                 ics_clock_paused = TRUE;
3773                 StopClocks();
3774                 continue;
3775             }
3776
3777             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3778                 ics_clock_paused = FALSE;
3779                 StartClocks();
3780                 continue;
3781             }
3782
3783             /* Grab player ratings from the Creating: message.
3784                Note we have to check for the special case when
3785                the ICS inserts things like [white] or [black]. */
3786             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3787                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3788                 /* star_matches:
3789                    0    player 1 name (not necessarily white)
3790                    1    player 1 rating
3791                    2    empty, white, or black (IGNORED)
3792                    3    player 2 name (not necessarily black)
3793                    4    player 2 rating
3794
3795                    The names/ratings are sorted out when the game
3796                    actually starts (below).
3797                 */
3798                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3799                 player1Rating = string_to_rating(star_match[1]);
3800                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3801                 player2Rating = string_to_rating(star_match[4]);
3802
3803                 if (appData.debugMode)
3804                   fprintf(debugFP,
3805                           "Ratings from 'Creating:' %s %d, %s %d\n",
3806                           player1Name, player1Rating,
3807                           player2Name, player2Rating);
3808
3809                 continue;
3810             }
3811
3812             /* Improved generic start/end-of-game messages */
3813             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3814                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3815                 /* If tkind == 0: */
3816                 /* star_match[0] is the game number */
3817                 /*           [1] is the white player's name */
3818                 /*           [2] is the black player's name */
3819                 /* For end-of-game: */
3820                 /*           [3] is the reason for the game end */
3821                 /*           [4] is a PGN end game-token, preceded by " " */
3822                 /* For start-of-game: */
3823                 /*           [3] begins with "Creating" or "Continuing" */
3824                 /*           [4] is " *" or empty (don't care). */
3825                 int gamenum = atoi(star_match[0]);
3826                 char *whitename, *blackname, *why, *endtoken;
3827                 ChessMove endtype = EndOfFile;
3828
3829                 if (tkind == 0) {
3830                   whitename = star_match[1];
3831                   blackname = star_match[2];
3832                   why = star_match[3];
3833                   endtoken = star_match[4];
3834                 } else {
3835                   whitename = star_match[1];
3836                   blackname = star_match[3];
3837                   why = star_match[5];
3838                   endtoken = star_match[6];
3839                 }
3840
3841                 /* Game start messages */
3842                 if (strncmp(why, "Creating ", 9) == 0 ||
3843                     strncmp(why, "Continuing ", 11) == 0) {
3844                     gs_gamenum = gamenum;
3845                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3846                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3847 #if ZIPPY
3848                     if (appData.zippyPlay) {
3849                         ZippyGameStart(whitename, blackname);
3850                     }
3851 #endif /*ZIPPY*/
3852                     partnerBoardValid = FALSE; // [HGM] bughouse
3853                     continue;
3854                 }
3855
3856                 /* Game end messages */
3857                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3858                     ics_gamenum != gamenum) {
3859                     continue;
3860                 }
3861                 while (endtoken[0] == ' ') endtoken++;
3862                 switch (endtoken[0]) {
3863                   case '*':
3864                   default:
3865                     endtype = GameUnfinished;
3866                     break;
3867                   case '0':
3868                     endtype = BlackWins;
3869                     break;
3870                   case '1':
3871                     if (endtoken[1] == '/')
3872                       endtype = GameIsDrawn;
3873                     else
3874                       endtype = WhiteWins;
3875                     break;
3876                 }
3877                 GameEnds(endtype, why, GE_ICS);
3878 #if ZIPPY
3879                 if (appData.zippyPlay && first.initDone) {
3880                     ZippyGameEnd(endtype, why);
3881                     if (first.pr == NULL) {
3882                       /* Start the next process early so that we'll
3883                          be ready for the next challenge */
3884                       StartChessProgram(&first);
3885                     }
3886                     /* Send "new" early, in case this command takes
3887                        a long time to finish, so that we'll be ready
3888                        for the next challenge. */
3889                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3890                     Reset(TRUE, TRUE);
3891                 }
3892 #endif /*ZIPPY*/
3893                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3894                 continue;
3895             }
3896
3897             if (looking_at(buf, &i, "Removing game * from observation") ||
3898                 looking_at(buf, &i, "no longer observing game *") ||
3899                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3900                 if (gameMode == IcsObserving &&
3901                     atoi(star_match[0]) == ics_gamenum)
3902                   {
3903                       /* icsEngineAnalyze */
3904                       if (appData.icsEngineAnalyze) {
3905                             ExitAnalyzeMode();
3906                             ModeHighlight();
3907                       }
3908                       StopClocks();
3909                       gameMode = IcsIdle;
3910                       ics_gamenum = -1;
3911                       ics_user_moved = FALSE;
3912                   }
3913                 continue;
3914             }
3915
3916             if (looking_at(buf, &i, "no longer examining game *")) {
3917                 if (gameMode == IcsExamining &&
3918                     atoi(star_match[0]) == ics_gamenum)
3919                   {
3920                       gameMode = IcsIdle;
3921                       ics_gamenum = -1;
3922                       ics_user_moved = FALSE;
3923                   }
3924                 continue;
3925             }
3926
3927             /* Advance leftover_start past any newlines we find,
3928                so only partial lines can get reparsed */
3929             if (looking_at(buf, &i, "\n")) {
3930                 prevColor = curColor;
3931                 if (curColor != ColorNormal) {
3932                     if (oldi > next_out) {
3933                         SendToPlayer(&buf[next_out], oldi - next_out);
3934                         next_out = oldi;
3935                     }
3936                     Colorize(ColorNormal, FALSE);
3937                     curColor = ColorNormal;
3938                 }
3939                 if (started == STARTED_BOARD) {
3940                     started = STARTED_NONE;
3941                     parse[parse_pos] = NULLCHAR;
3942                     ParseBoard12(parse);
3943                     ics_user_moved = 0;
3944
3945                     /* Send premove here */
3946                     if (appData.premove) {
3947                       char str[MSG_SIZ];
3948                       if (currentMove == 0 &&
3949                           gameMode == IcsPlayingWhite &&
3950                           appData.premoveWhite) {
3951                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3952                         if (appData.debugMode)
3953                           fprintf(debugFP, "Sending premove:\n");
3954                         SendToICS(str);
3955                       } else if (currentMove == 1 &&
3956                                  gameMode == IcsPlayingBlack &&
3957                                  appData.premoveBlack) {
3958                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3959                         if (appData.debugMode)
3960                           fprintf(debugFP, "Sending premove:\n");
3961                         SendToICS(str);
3962                       } else if (gotPremove) {
3963                         gotPremove = 0;
3964                         ClearPremoveHighlights();
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                           UserMoveEvent(premoveFromX, premoveFromY,
3968                                         premoveToX, premoveToY,
3969                                         premovePromoChar);
3970                       }
3971                     }
3972
3973                     /* Usually suppress following prompt */
3974                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3975                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3976                         if (looking_at(buf, &i, "*% ")) {
3977                             savingComment = FALSE;
3978                             suppressKibitz = 0;
3979                         }
3980                     }
3981                     next_out = i;
3982                 } else if (started == STARTED_HOLDINGS) {
3983                     int gamenum;
3984                     char new_piece[MSG_SIZ];
3985                     started = STARTED_NONE;
3986                     parse[parse_pos] = NULLCHAR;
3987                     if (appData.debugMode)
3988                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3989                                                         parse, currentMove);
3990                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3991                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3992                         if (gameInfo.variant == VariantNormal) {
3993                           /* [HGM] We seem to switch variant during a game!
3994                            * Presumably no holdings were displayed, so we have
3995                            * to move the position two files to the right to
3996                            * create room for them!
3997                            */
3998                           VariantClass newVariant;
3999                           switch(gameInfo.boardWidth) { // base guess on board width
4000                                 case 9:  newVariant = VariantShogi; break;
4001                                 case 10: newVariant = VariantGreat; break;
4002                                 default: newVariant = VariantCrazyhouse; break;
4003                           }
4004                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4005                           /* Get a move list just to see the header, which
4006                              will tell us whether this is really bug or zh */
4007                           if (ics_getting_history == H_FALSE) {
4008                             ics_getting_history = H_REQUESTED;
4009                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4010                             SendToICS(str);
4011                           }
4012                         }
4013                         new_piece[0] = NULLCHAR;
4014                         sscanf(parse, "game %d white [%s black [%s <- %s",
4015                                &gamenum, white_holding, black_holding,
4016                                new_piece);
4017                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4018                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4019                         /* [HGM] copy holdings to board holdings area */
4020                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4021                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4022                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4023 #if ZIPPY
4024                         if (appData.zippyPlay && first.initDone) {
4025                             ZippyHoldings(white_holding, black_holding,
4026                                           new_piece);
4027                         }
4028 #endif /*ZIPPY*/
4029                         if (tinyLayout || smallLayout) {
4030                             char wh[16], bh[16];
4031                             PackHolding(wh, white_holding);
4032                             PackHolding(bh, black_holding);
4033                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4034                                     gameInfo.white, gameInfo.black);
4035                         } else {
4036                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4037                                     gameInfo.white, white_holding,
4038                                     gameInfo.black, black_holding);
4039                         }
4040                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4041                         DrawPosition(FALSE, boards[currentMove]);
4042                         DisplayTitle(str);
4043                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4044                         sscanf(parse, "game %d white [%s black [%s <- %s",
4045                                &gamenum, white_holding, black_holding,
4046                                new_piece);
4047                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4048                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4049                         /* [HGM] copy holdings to partner-board holdings area */
4050                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4051                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4052                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4053                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4054                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4055                       }
4056                     }
4057                     /* Suppress following prompt */
4058                     if (looking_at(buf, &i, "*% ")) {
4059                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4060                         savingComment = FALSE;
4061                         suppressKibitz = 0;
4062                     }
4063                     next_out = i;
4064                 }
4065                 continue;
4066             }
4067
4068             i++;                /* skip unparsed character and loop back */
4069         }
4070
4071         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4072 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4073 //          SendToPlayer(&buf[next_out], i - next_out);
4074             started != STARTED_HOLDINGS && leftover_start > next_out) {
4075             SendToPlayer(&buf[next_out], leftover_start - next_out);
4076             next_out = i;
4077         }
4078
4079         leftover_len = buf_len - leftover_start;
4080         /* if buffer ends with something we couldn't parse,
4081            reparse it after appending the next read */
4082
4083     } else if (count == 0) {
4084         RemoveInputSource(isr);
4085         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4086     } else {
4087         DisplayFatalError(_("Error reading from ICS"), error, 1);
4088     }
4089 }
4090
4091
4092 /* Board style 12 looks like this:
4093
4094    <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
4095
4096  * The "<12> " is stripped before it gets to this routine.  The two
4097  * trailing 0's (flip state and clock ticking) are later addition, and
4098  * some chess servers may not have them, or may have only the first.
4099  * Additional trailing fields may be added in the future.
4100  */
4101
4102 #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"
4103
4104 #define RELATION_OBSERVING_PLAYED    0
4105 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4106 #define RELATION_PLAYING_MYMOVE      1
4107 #define RELATION_PLAYING_NOTMYMOVE  -1
4108 #define RELATION_EXAMINING           2
4109 #define RELATION_ISOLATED_BOARD     -3
4110 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4111
4112 void
4113 ParseBoard12(string)
4114      char *string;
4115 {
4116     GameMode newGameMode;
4117     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4118     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4119     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4120     char to_play, board_chars[200];
4121     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4122     char black[32], white[32];
4123     Board board;
4124     int prevMove = currentMove;
4125     int ticking = 2;
4126     ChessMove moveType;
4127     int fromX, fromY, toX, toY;
4128     char promoChar;
4129     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4130     char *bookHit = NULL; // [HGM] book
4131     Boolean weird = FALSE, reqFlag = FALSE;
4132
4133     fromX = fromY = toX = toY = -1;
4134
4135     newGame = FALSE;
4136
4137     if (appData.debugMode)
4138       fprintf(debugFP, _("Parsing board: %s\n"), string);
4139
4140     move_str[0] = NULLCHAR;
4141     elapsed_time[0] = NULLCHAR;
4142     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4143         int  i = 0, j;
4144         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4145             if(string[i] == ' ') { ranks++; files = 0; }
4146             else files++;
4147             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4148             i++;
4149         }
4150         for(j = 0; j <i; j++) board_chars[j] = string[j];
4151         board_chars[i] = '\0';
4152         string += i + 1;
4153     }
4154     n = sscanf(string, PATTERN, &to_play, &double_push,
4155                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4156                &gamenum, white, black, &relation, &basetime, &increment,
4157                &white_stren, &black_stren, &white_time, &black_time,
4158                &moveNum, str, elapsed_time, move_str, &ics_flip,
4159                &ticking);
4160
4161     if (n < 21) {
4162         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4163         DisplayError(str, 0);
4164         return;
4165     }
4166
4167     /* Convert the move number to internal form */
4168     moveNum = (moveNum - 1) * 2;
4169     if (to_play == 'B') moveNum++;
4170     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4171       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4172                         0, 1);
4173       return;
4174     }
4175
4176     switch (relation) {
4177       case RELATION_OBSERVING_PLAYED:
4178       case RELATION_OBSERVING_STATIC:
4179         if (gamenum == -1) {
4180             /* Old ICC buglet */
4181             relation = RELATION_OBSERVING_STATIC;
4182         }
4183         newGameMode = IcsObserving;
4184         break;
4185       case RELATION_PLAYING_MYMOVE:
4186       case RELATION_PLAYING_NOTMYMOVE:
4187         newGameMode =
4188           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4189             IcsPlayingWhite : IcsPlayingBlack;
4190         break;
4191       case RELATION_EXAMINING:
4192         newGameMode = IcsExamining;
4193         break;
4194       case RELATION_ISOLATED_BOARD:
4195       default:
4196         /* Just display this board.  If user was doing something else,
4197            we will forget about it until the next board comes. */
4198         newGameMode = IcsIdle;
4199         break;
4200       case RELATION_STARTING_POSITION:
4201         newGameMode = gameMode;
4202         break;
4203     }
4204
4205     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4206          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4207       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4208       char *toSqr;
4209       for (k = 0; k < ranks; k++) {
4210         for (j = 0; j < files; j++)
4211           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4212         if(gameInfo.holdingsWidth > 1) {
4213              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4214              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4215         }
4216       }
4217       CopyBoard(partnerBoard, board);
4218       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4219         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4220         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4221       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4222       if(toSqr = strchr(str, '-')) {
4223         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4224         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4225       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4226       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4227       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4228       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4229       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4230       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4231                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4232       DisplayMessage(partnerStatus, "");
4233         partnerBoardValid = TRUE;
4234       return;
4235     }
4236
4237     /* Modify behavior for initial board display on move listing
4238        of wild games.
4239        */
4240     switch (ics_getting_history) {
4241       case H_FALSE:
4242       case H_REQUESTED:
4243         break;
4244       case H_GOT_REQ_HEADER:
4245       case H_GOT_UNREQ_HEADER:
4246         /* This is the initial position of the current game */
4247         gamenum = ics_gamenum;
4248         moveNum = 0;            /* old ICS bug workaround */
4249         if (to_play == 'B') {
4250           startedFromSetupPosition = TRUE;
4251           blackPlaysFirst = TRUE;
4252           moveNum = 1;
4253           if (forwardMostMove == 0) forwardMostMove = 1;
4254           if (backwardMostMove == 0) backwardMostMove = 1;
4255           if (currentMove == 0) currentMove = 1;
4256         }
4257         newGameMode = gameMode;
4258         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4259         break;
4260       case H_GOT_UNWANTED_HEADER:
4261         /* This is an initial board that we don't want */
4262         return;
4263       case H_GETTING_MOVES:
4264         /* Should not happen */
4265         DisplayError(_("Error gathering move list: extra board"), 0);
4266         ics_getting_history = H_FALSE;
4267         return;
4268     }
4269
4270    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4271                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4272      /* [HGM] We seem to have switched variant unexpectedly
4273       * Try to guess new variant from board size
4274       */
4275           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4276           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4277           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4278           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4279           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4280           if(!weird) newVariant = VariantNormal;
4281           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4282           /* Get a move list just to see the header, which
4283              will tell us whether this is really bug or zh */
4284           if (ics_getting_history == H_FALSE) {
4285             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4286             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4287             SendToICS(str);
4288           }
4289     }
4290
4291     /* Take action if this is the first board of a new game, or of a
4292        different game than is currently being displayed.  */
4293     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4294         relation == RELATION_ISOLATED_BOARD) {
4295
4296         /* Forget the old game and get the history (if any) of the new one */
4297         if (gameMode != BeginningOfGame) {
4298           Reset(TRUE, TRUE);
4299         }
4300         newGame = TRUE;
4301         if (appData.autoRaiseBoard) BoardToTop();
4302         prevMove = -3;
4303         if (gamenum == -1) {
4304             newGameMode = IcsIdle;
4305         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4306                    appData.getMoveList && !reqFlag) {
4307             /* Need to get game history */
4308             ics_getting_history = H_REQUESTED;
4309             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4310             SendToICS(str);
4311         }
4312
4313         /* Initially flip the board to have black on the bottom if playing
4314            black or if the ICS flip flag is set, but let the user change
4315            it with the Flip View button. */
4316         flipView = appData.autoFlipView ?
4317           (newGameMode == IcsPlayingBlack) || ics_flip :
4318           appData.flipView;
4319
4320         /* Done with values from previous mode; copy in new ones */
4321         gameMode = newGameMode;
4322         ModeHighlight();
4323         ics_gamenum = gamenum;
4324         if (gamenum == gs_gamenum) {
4325             int klen = strlen(gs_kind);
4326             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4327             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4328             gameInfo.event = StrSave(str);
4329         } else {
4330             gameInfo.event = StrSave("ICS game");
4331         }
4332         gameInfo.site = StrSave(appData.icsHost);
4333         gameInfo.date = PGNDate();
4334         gameInfo.round = StrSave("-");
4335         gameInfo.white = StrSave(white);
4336         gameInfo.black = StrSave(black);
4337         timeControl = basetime * 60 * 1000;
4338         timeControl_2 = 0;
4339         timeIncrement = increment * 1000;
4340         movesPerSession = 0;
4341         gameInfo.timeControl = TimeControlTagValue();
4342         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4343   if (appData.debugMode) {
4344     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4345     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4346     setbuf(debugFP, NULL);
4347   }
4348
4349         gameInfo.outOfBook = NULL;
4350
4351         /* Do we have the ratings? */
4352         if (strcmp(player1Name, white) == 0 &&
4353             strcmp(player2Name, black) == 0) {
4354             if (appData.debugMode)
4355               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4356                       player1Rating, player2Rating);
4357             gameInfo.whiteRating = player1Rating;
4358             gameInfo.blackRating = player2Rating;
4359         } else if (strcmp(player2Name, white) == 0 &&
4360                    strcmp(player1Name, black) == 0) {
4361             if (appData.debugMode)
4362               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4363                       player2Rating, player1Rating);
4364             gameInfo.whiteRating = player2Rating;
4365             gameInfo.blackRating = player1Rating;
4366         }
4367         player1Name[0] = player2Name[0] = NULLCHAR;
4368
4369         /* Silence shouts if requested */
4370         if (appData.quietPlay &&
4371             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4372             SendToICS(ics_prefix);
4373             SendToICS("set shout 0\n");
4374         }
4375     }
4376
4377     /* Deal with midgame name changes */
4378     if (!newGame) {
4379         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4380             if (gameInfo.white) free(gameInfo.white);
4381             gameInfo.white = StrSave(white);
4382         }
4383         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4384             if (gameInfo.black) free(gameInfo.black);
4385             gameInfo.black = StrSave(black);
4386         }
4387     }
4388
4389     /* Throw away game result if anything actually changes in examine mode */
4390     if (gameMode == IcsExamining && !newGame) {
4391         gameInfo.result = GameUnfinished;
4392         if (gameInfo.resultDetails != NULL) {
4393             free(gameInfo.resultDetails);
4394             gameInfo.resultDetails = NULL;
4395         }
4396     }
4397
4398     /* In pausing && IcsExamining mode, we ignore boards coming
4399        in if they are in a different variation than we are. */
4400     if (pauseExamInvalid) return;
4401     if (pausing && gameMode == IcsExamining) {
4402         if (moveNum <= pauseExamForwardMostMove) {
4403             pauseExamInvalid = TRUE;
4404             forwardMostMove = pauseExamForwardMostMove;
4405             return;
4406         }
4407     }
4408
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4411   }
4412     /* Parse the board */
4413     for (k = 0; k < ranks; k++) {
4414       for (j = 0; j < files; j++)
4415         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4416       if(gameInfo.holdingsWidth > 1) {
4417            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4418            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4419       }
4420     }
4421     CopyBoard(boards[moveNum], board);
4422     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4423     if (moveNum == 0) {
4424         startedFromSetupPosition =
4425           !CompareBoards(board, initialPosition);
4426         if(startedFromSetupPosition)
4427             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4428     }
4429
4430     /* [HGM] Set castling rights. Take the outermost Rooks,
4431        to make it also work for FRC opening positions. Note that board12
4432        is really defective for later FRC positions, as it has no way to
4433        indicate which Rook can castle if they are on the same side of King.
4434        For the initial position we grant rights to the outermost Rooks,
4435        and remember thos rights, and we then copy them on positions
4436        later in an FRC game. This means WB might not recognize castlings with
4437        Rooks that have moved back to their original position as illegal,
4438        but in ICS mode that is not its job anyway.
4439     */
4440     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4441     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4442
4443         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4444             if(board[0][i] == WhiteRook) j = i;
4445         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4446         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4447             if(board[0][i] == WhiteRook) j = i;
4448         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4449         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4450             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4451         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4452         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4453             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4454         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4455
4456         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4457         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4458             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4459         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4460             if(board[BOARD_HEIGHT-1][k] == bKing)
4461                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4462         if(gameInfo.variant == VariantTwoKings) {
4463             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4464             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4465             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4466         }
4467     } else { int r;
4468         r = boards[moveNum][CASTLING][0] = initialRights[0];
4469         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4470         r = boards[moveNum][CASTLING][1] = initialRights[1];
4471         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4472         r = boards[moveNum][CASTLING][3] = initialRights[3];
4473         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4474         r = boards[moveNum][CASTLING][4] = initialRights[4];
4475         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4476         /* wildcastle kludge: always assume King has rights */
4477         r = boards[moveNum][CASTLING][2] = initialRights[2];
4478         r = boards[moveNum][CASTLING][5] = initialRights[5];
4479     }
4480     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4481     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4482
4483
4484     if (ics_getting_history == H_GOT_REQ_HEADER ||
4485         ics_getting_history == H_GOT_UNREQ_HEADER) {
4486         /* This was an initial position from a move list, not
4487            the current position */
4488         return;
4489     }
4490
4491     /* Update currentMove and known move number limits */
4492     newMove = newGame || moveNum > forwardMostMove;
4493
4494     if (newGame) {
4495         forwardMostMove = backwardMostMove = currentMove = moveNum;
4496         if (gameMode == IcsExamining && moveNum == 0) {
4497           /* Workaround for ICS limitation: we are not told the wild
4498              type when starting to examine a game.  But if we ask for
4499              the move list, the move list header will tell us */
4500             ics_getting_history = H_REQUESTED;
4501             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4502             SendToICS(str);
4503         }
4504     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4505                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4506 #if ZIPPY
4507         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4508         /* [HGM] applied this also to an engine that is silently watching        */
4509         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4510             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4511             gameInfo.variant == currentlyInitializedVariant) {
4512           takeback = forwardMostMove - moveNum;
4513           for (i = 0; i < takeback; i++) {
4514             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4515             SendToProgram("undo\n", &first);
4516           }
4517         }
4518 #endif
4519
4520         forwardMostMove = moveNum;
4521         if (!pausing || currentMove > forwardMostMove)
4522           currentMove = forwardMostMove;
4523     } else {
4524         /* New part of history that is not contiguous with old part */
4525         if (pausing && gameMode == IcsExamining) {
4526             pauseExamInvalid = TRUE;
4527             forwardMostMove = pauseExamForwardMostMove;
4528             return;
4529         }
4530         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4531 #if ZIPPY
4532             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4533                 // [HGM] when we will receive the move list we now request, it will be
4534                 // fed to the engine from the first move on. So if the engine is not
4535                 // in the initial position now, bring it there.
4536                 InitChessProgram(&first, 0);
4537             }
4538 #endif
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543         forwardMostMove = backwardMostMove = currentMove = moveNum;
4544     }
4545
4546     /* Update the clocks */
4547     if (strchr(elapsed_time, '.')) {
4548       /* Time is in ms */
4549       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4550       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4551     } else {
4552       /* Time is in seconds */
4553       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4554       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4555     }
4556
4557
4558 #if ZIPPY
4559     if (appData.zippyPlay && newGame &&
4560         gameMode != IcsObserving && gameMode != IcsIdle &&
4561         gameMode != IcsExamining)
4562       ZippyFirstBoard(moveNum, basetime, increment);
4563 #endif
4564
4565     /* Put the move on the move list, first converting
4566        to canonical algebraic form. */
4567     if (moveNum > 0) {
4568   if (appData.debugMode) {
4569     if (appData.debugMode) { int f = forwardMostMove;
4570         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4571                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4572                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4573     }
4574     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4575     fprintf(debugFP, "moveNum = %d\n", moveNum);
4576     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4577     setbuf(debugFP, NULL);
4578   }
4579         if (moveNum <= backwardMostMove) {
4580             /* We don't know what the board looked like before
4581                this move.  Punt. */
4582           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4583             strcat(parseList[moveNum - 1], " ");
4584             strcat(parseList[moveNum - 1], elapsed_time);
4585             moveList[moveNum - 1][0] = NULLCHAR;
4586         } else if (strcmp(move_str, "none") == 0) {
4587             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4588             /* Again, we don't know what the board looked like;
4589                this is really the start of the game. */
4590             parseList[moveNum - 1][0] = NULLCHAR;
4591             moveList[moveNum - 1][0] = NULLCHAR;
4592             backwardMostMove = moveNum;
4593             startedFromSetupPosition = TRUE;
4594             fromX = fromY = toX = toY = -1;
4595         } else {
4596           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4597           //                 So we parse the long-algebraic move string in stead of the SAN move
4598           int valid; char buf[MSG_SIZ], *prom;
4599
4600           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4601                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4602           // str looks something like "Q/a1-a2"; kill the slash
4603           if(str[1] == '/')
4604             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4605           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4606           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4607                 strcat(buf, prom); // long move lacks promo specification!
4608           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4609                 if(appData.debugMode)
4610                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4611                 safeStrCpy(move_str, buf, MSG_SIZ);
4612           }
4613           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4614                                 &fromX, &fromY, &toX, &toY, &promoChar)
4615                || ParseOneMove(buf, moveNum - 1, &moveType,
4616                                 &fromX, &fromY, &toX, &toY, &promoChar);
4617           // end of long SAN patch
4618           if (valid) {
4619             (void) CoordsToAlgebraic(boards[moveNum - 1],
4620                                      PosFlags(moveNum - 1),
4621                                      fromY, fromX, toY, toX, promoChar,
4622                                      parseList[moveNum-1]);
4623             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4624               case MT_NONE:
4625               case MT_STALEMATE:
4626               default:
4627                 break;
4628               case MT_CHECK:
4629                 if(gameInfo.variant != VariantShogi)
4630                     strcat(parseList[moveNum - 1], "+");
4631                 break;
4632               case MT_CHECKMATE:
4633               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4634                 strcat(parseList[moveNum - 1], "#");
4635                 break;
4636             }
4637             strcat(parseList[moveNum - 1], " ");
4638             strcat(parseList[moveNum - 1], elapsed_time);
4639             /* currentMoveString is set as a side-effect of ParseOneMove */
4640             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4641             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4642             strcat(moveList[moveNum - 1], "\n");
4643
4644             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4645                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4646               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4647                 ChessSquare old, new = boards[moveNum][k][j];
4648                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4649                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4650                   if(old == new) continue;
4651                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4652                   else if(new == WhiteWazir || new == BlackWazir) {
4653                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4654                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4655                       else boards[moveNum][k][j] = old; // preserve type of Gold
4656                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4657                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4658               }
4659           } else {
4660             /* Move from ICS was illegal!?  Punt. */
4661             if (appData.debugMode) {
4662               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4663               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4664             }
4665             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4666             strcat(parseList[moveNum - 1], " ");
4667             strcat(parseList[moveNum - 1], elapsed_time);
4668             moveList[moveNum - 1][0] = NULLCHAR;
4669             fromX = fromY = toX = toY = -1;
4670           }
4671         }
4672   if (appData.debugMode) {
4673     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4674     setbuf(debugFP, NULL);
4675   }
4676
4677 #if ZIPPY
4678         /* Send move to chess program (BEFORE animating it). */
4679         if (appData.zippyPlay && !newGame && newMove &&
4680            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4681
4682             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4683                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4684                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4685                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4686                             move_str);
4687                     DisplayError(str, 0);
4688                 } else {
4689                     if (first.sendTime) {
4690                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4691                     }
4692                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4693                     if (firstMove && !bookHit) {
4694                         firstMove = FALSE;
4695                         if (first.useColors) {
4696                           SendToProgram(gameMode == IcsPlayingWhite ?
4697                                         "white\ngo\n" :
4698                                         "black\ngo\n", &first);
4699                         } else {
4700                           SendToProgram("go\n", &first);
4701                         }
4702                         first.maybeThinking = TRUE;
4703                     }
4704                 }
4705             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4706               if (moveList[moveNum - 1][0] == NULLCHAR) {
4707                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4708                 DisplayError(str, 0);
4709               } else {
4710                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4711                 SendMoveToProgram(moveNum - 1, &first);
4712               }
4713             }
4714         }
4715 #endif
4716     }
4717
4718     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4719         /* If move comes from a remote source, animate it.  If it
4720            isn't remote, it will have already been animated. */
4721         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4722             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4723         }
4724         if (!pausing && appData.highlightLastMove) {
4725             SetHighlights(fromX, fromY, toX, toY);
4726         }
4727     }
4728
4729     /* Start the clocks */
4730     whiteFlag = blackFlag = FALSE;
4731     appData.clockMode = !(basetime == 0 && increment == 0);
4732     if (ticking == 0) {
4733       ics_clock_paused = TRUE;
4734       StopClocks();
4735     } else if (ticking == 1) {
4736       ics_clock_paused = FALSE;
4737     }
4738     if (gameMode == IcsIdle ||
4739         relation == RELATION_OBSERVING_STATIC ||
4740         relation == RELATION_EXAMINING ||
4741         ics_clock_paused)
4742       DisplayBothClocks();
4743     else
4744       StartClocks();
4745
4746     /* Display opponents and material strengths */
4747     if (gameInfo.variant != VariantBughouse &&
4748         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4749         if (tinyLayout || smallLayout) {
4750             if(gameInfo.variant == VariantNormal)
4751               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4752                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4753                     basetime, increment);
4754             else
4755               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4756                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4757                     basetime, increment, (int) gameInfo.variant);
4758         } else {
4759             if(gameInfo.variant == VariantNormal)
4760               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4761                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4762                     basetime, increment);
4763             else
4764               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment, VariantName(gameInfo.variant));
4767         }
4768         DisplayTitle(str);
4769   if (appData.debugMode) {
4770     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4771   }
4772     }
4773
4774
4775     /* Display the board */
4776     if (!pausing && !appData.noGUI) {
4777
4778       if (appData.premove)
4779           if (!gotPremove ||
4780              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4781              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4782               ClearPremoveHighlights();
4783
4784       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4785         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4786       DrawPosition(j, boards[currentMove]);
4787
4788       DisplayMove(moveNum - 1);
4789       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4790             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4791               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4792         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4793       }
4794     }
4795
4796     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4797 #if ZIPPY
4798     if(bookHit) { // [HGM] book: simulate book reply
4799         static char bookMove[MSG_SIZ]; // a bit generous?
4800
4801         programStats.nodes = programStats.depth = programStats.time =
4802         programStats.score = programStats.got_only_move = 0;
4803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4804
4805         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4806         strcat(bookMove, bookHit);
4807         HandleMachineMove(bookMove, &first);
4808     }
4809 #endif
4810 }
4811
4812 void
4813 GetMoveListEvent()
4814 {
4815     char buf[MSG_SIZ];
4816     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4817         ics_getting_history = H_REQUESTED;
4818         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4819         SendToICS(buf);
4820     }
4821 }
4822
4823 void
4824 AnalysisPeriodicEvent(force)
4825      int force;
4826 {
4827     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4828          && !force) || !appData.periodicUpdates)
4829       return;
4830
4831     /* Send . command to Crafty to collect stats */
4832     SendToProgram(".\n", &first);
4833
4834     /* Don't send another until we get a response (this makes
4835        us stop sending to old Crafty's which don't understand
4836        the "." command (sending illegal cmds resets node count & time,
4837        which looks bad)) */
4838     programStats.ok_to_send = 0;
4839 }
4840
4841 void ics_update_width(new_width)
4842         int new_width;
4843 {
4844         ics_printf("set width %d\n", new_width);
4845 }
4846
4847 void
4848 SendMoveToProgram(moveNum, cps)
4849      int moveNum;
4850      ChessProgramState *cps;
4851 {
4852     char buf[MSG_SIZ];
4853
4854     if (cps->useUsermove) {
4855       SendToProgram("usermove ", cps);
4856     }
4857     if (cps->useSAN) {
4858       char *space;
4859       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4860         int len = space - parseList[moveNum];
4861         memcpy(buf, parseList[moveNum], len);
4862         buf[len++] = '\n';
4863         buf[len] = NULLCHAR;
4864       } else {
4865         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4866       }
4867       SendToProgram(buf, cps);
4868     } else {
4869       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4870         AlphaRank(moveList[moveNum], 4);
4871         SendToProgram(moveList[moveNum], cps);
4872         AlphaRank(moveList[moveNum], 4); // and back
4873       } else
4874       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4875        * the engine. It would be nice to have a better way to identify castle
4876        * moves here. */
4877       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4878                                                                          && cps->useOOCastle) {
4879         int fromX = moveList[moveNum][0] - AAA;
4880         int fromY = moveList[moveNum][1] - ONE;
4881         int toX = moveList[moveNum][2] - AAA;
4882         int toY = moveList[moveNum][3] - ONE;
4883         if((boards[moveNum][fromY][fromX] == WhiteKing
4884             && boards[moveNum][toY][toX] == WhiteRook)
4885            || (boards[moveNum][fromY][fromX] == BlackKing
4886                && boards[moveNum][toY][toX] == BlackRook)) {
4887           if(toX > fromX) SendToProgram("O-O\n", cps);
4888           else SendToProgram("O-O-O\n", cps);
4889         }
4890         else SendToProgram(moveList[moveNum], cps);
4891       }
4892       else SendToProgram(moveList[moveNum], cps);
4893       /* End of additions by Tord */
4894     }
4895
4896     /* [HGM] setting up the opening has brought engine in force mode! */
4897     /*       Send 'go' if we are in a mode where machine should play. */
4898     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4899         (gameMode == TwoMachinesPlay   ||
4900 #if ZIPPY
4901          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4902 #endif
4903          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4904         SendToProgram("go\n", cps);
4905   if (appData.debugMode) {
4906     fprintf(debugFP, "(extra)\n");
4907   }
4908     }
4909     setboardSpoiledMachineBlack = 0;
4910 }
4911
4912 void
4913 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4914      ChessMove moveType;
4915      int fromX, fromY, toX, toY;
4916      char promoChar;
4917 {
4918     char user_move[MSG_SIZ];
4919
4920     switch (moveType) {
4921       default:
4922         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4923                 (int)moveType, fromX, fromY, toX, toY);
4924         DisplayError(user_move + strlen("say "), 0);
4925         break;
4926       case WhiteKingSideCastle:
4927       case BlackKingSideCastle:
4928       case WhiteQueenSideCastleWild:
4929       case BlackQueenSideCastleWild:
4930       /* PUSH Fabien */
4931       case WhiteHSideCastleFR:
4932       case BlackHSideCastleFR:
4933       /* POP Fabien */
4934         snprintf(user_move, MSG_SIZ, "o-o\n");
4935         break;
4936       case WhiteQueenSideCastle:
4937       case BlackQueenSideCastle:
4938       case WhiteKingSideCastleWild:
4939       case BlackKingSideCastleWild:
4940       /* PUSH Fabien */
4941       case WhiteASideCastleFR:
4942       case BlackASideCastleFR:
4943       /* POP Fabien */
4944         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4945         break;
4946       case WhiteNonPromotion:
4947       case BlackNonPromotion:
4948         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4949         break;
4950       case WhitePromotion:
4951       case BlackPromotion:
4952         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4953           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4954                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4955                 PieceToChar(WhiteFerz));
4956         else if(gameInfo.variant == VariantGreat)
4957           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4958                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4959                 PieceToChar(WhiteMan));
4960         else
4961           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4962                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4963                 promoChar);
4964         break;
4965       case WhiteDrop:
4966       case BlackDrop:
4967       drop:
4968         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4969                  ToUpper(PieceToChar((ChessSquare) fromX)),
4970                  AAA + toX, ONE + toY);
4971         break;
4972       case IllegalMove:  /* could be a variant we don't quite understand */
4973         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4974       case NormalMove:
4975       case WhiteCapturesEnPassant:
4976       case BlackCapturesEnPassant:
4977         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4978                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4979         break;
4980     }
4981     SendToICS(user_move);
4982     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4983         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4984 }
4985
4986 void
4987 UploadGameEvent()
4988 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4989     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4990     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4991     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4992         DisplayError("You cannot do this while you are playing or observing", 0);
4993         return;
4994     }
4995     if(gameMode != IcsExamining) { // is this ever not the case?
4996         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4997
4998         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4999           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5000         } else { // on FICS we must first go to general examine mode
5001           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5002         }
5003         if(gameInfo.variant != VariantNormal) {
5004             // try figure out wild number, as xboard names are not always valid on ICS
5005             for(i=1; i<=36; i++) {
5006               snprintf(buf, MSG_SIZ, "wild/%d", i);
5007                 if(StringToVariant(buf) == gameInfo.variant) break;
5008             }
5009             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5010             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5011             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5012         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5013         SendToICS(ics_prefix);
5014         SendToICS(buf);
5015         if(startedFromSetupPosition || backwardMostMove != 0) {
5016           fen = PositionToFEN(backwardMostMove, NULL);
5017           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5018             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5019             SendToICS(buf);
5020           } else { // FICS: everything has to set by separate bsetup commands
5021             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5022             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5023             SendToICS(buf);
5024             if(!WhiteOnMove(backwardMostMove)) {
5025                 SendToICS("bsetup tomove black\n");
5026             }
5027             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5028             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5029             SendToICS(buf);
5030             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5031             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5032             SendToICS(buf);
5033             i = boards[backwardMostMove][EP_STATUS];
5034             if(i >= 0) { // set e.p.
5035               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5036                 SendToICS(buf);
5037             }
5038             bsetup++;
5039           }
5040         }
5041       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5042             SendToICS("bsetup done\n"); // switch to normal examining.
5043     }
5044     for(i = backwardMostMove; i<last; i++) {
5045         char buf[20];
5046         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5047         SendToICS(buf);
5048     }
5049     SendToICS(ics_prefix);
5050     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5051 }
5052
5053 void
5054 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5055      int rf, ff, rt, ft;
5056      char promoChar;
5057      char move[7];
5058 {
5059     if (rf == DROP_RANK) {
5060       sprintf(move, "%c@%c%c\n",
5061                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5062     } else {
5063         if (promoChar == 'x' || promoChar == NULLCHAR) {
5064           sprintf(move, "%c%c%c%c\n",
5065                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5066         } else {
5067             sprintf(move, "%c%c%c%c%c\n",
5068                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5069         }
5070     }
5071 }
5072
5073 void
5074 ProcessICSInitScript(f)
5075      FILE *f;
5076 {
5077     char buf[MSG_SIZ];
5078
5079     while (fgets(buf, MSG_SIZ, f)) {
5080         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5081     }
5082
5083     fclose(f);
5084 }
5085
5086
5087 static int lastX, lastY, selectFlag, dragging;
5088
5089 void
5090 Sweep(int step)
5091 {
5092     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5093     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5094     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5095     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5096     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5097     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5098     do {
5099         promoSweep -= step;
5100         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5101         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5102         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5103         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5104         if(!step) step = 1;
5105     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5106             appData.testLegality && (promoSweep == king ||
5107             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5108     ChangeDragPiece(promoSweep);
5109 }
5110
5111 int PromoScroll(int x, int y)
5112 {
5113   int step = 0;
5114
5115   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5116   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5117   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5118   if(!step) return FALSE;
5119   lastX = x; lastY = y;
5120   if((promoSweep < BlackPawn) == flipView) step = -step;
5121   if(step > 0) selectFlag = 1;
5122   if(!selectFlag) Sweep(step);
5123   return FALSE;
5124 }
5125
5126 void
5127 NextPiece(int step)
5128 {
5129     ChessSquare piece = boards[currentMove][toY][toX];
5130     do {
5131         pieceSweep -= step;
5132         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5133         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5134         if(!step) step = -1;
5135     } while(PieceToChar(pieceSweep) == '.');
5136     boards[currentMove][toY][toX] = pieceSweep;
5137     DrawPosition(FALSE, boards[currentMove]);
5138     boards[currentMove][toY][toX] = piece;
5139 }
5140 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5141 void
5142 AlphaRank(char *move, int n)
5143 {
5144 //    char *p = move, c; int x, y;
5145
5146     if (appData.debugMode) {
5147         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5148     }
5149
5150     if(move[1]=='*' &&
5151        move[2]>='0' && move[2]<='9' &&
5152        move[3]>='a' && move[3]<='x'    ) {
5153         move[1] = '@';
5154         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5155         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5156     } else
5157     if(move[0]>='0' && move[0]<='9' &&
5158        move[1]>='a' && move[1]<='x' &&
5159        move[2]>='0' && move[2]<='9' &&
5160        move[3]>='a' && move[3]<='x'    ) {
5161         /* input move, Shogi -> normal */
5162         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5163         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5164         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5165         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5166     } else
5167     if(move[1]=='@' &&
5168        move[3]>='0' && move[3]<='9' &&
5169        move[2]>='a' && move[2]<='x'    ) {
5170         move[1] = '*';
5171         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5172         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5173     } else
5174     if(
5175        move[0]>='a' && move[0]<='x' &&
5176        move[3]>='0' && move[3]<='9' &&
5177        move[2]>='a' && move[2]<='x'    ) {
5178          /* output move, normal -> Shogi */
5179         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5180         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5181         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5182         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5183         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5184     }
5185     if (appData.debugMode) {
5186         fprintf(debugFP, "   out = '%s'\n", move);
5187     }
5188 }
5189
5190 char yy_textstr[8000];
5191
5192 /* Parser for moves from gnuchess, ICS, or user typein box */
5193 Boolean
5194 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5195      char *move;
5196      int moveNum;
5197      ChessMove *moveType;
5198      int *fromX, *fromY, *toX, *toY;
5199      char *promoChar;
5200 {
5201     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5202
5203     switch (*moveType) {
5204       case WhitePromotion:
5205       case BlackPromotion:
5206       case WhiteNonPromotion:
5207       case BlackNonPromotion:
5208       case NormalMove:
5209       case WhiteCapturesEnPassant:
5210       case BlackCapturesEnPassant:
5211       case WhiteKingSideCastle:
5212       case WhiteQueenSideCastle:
5213       case BlackKingSideCastle:
5214       case BlackQueenSideCastle:
5215       case WhiteKingSideCastleWild:
5216       case WhiteQueenSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       case BlackQueenSideCastleWild:
5219       /* Code added by Tord: */
5220       case WhiteHSideCastleFR:
5221       case WhiteASideCastleFR:
5222       case BlackHSideCastleFR:
5223       case BlackASideCastleFR:
5224       /* End of code added by Tord */
5225       case IllegalMove:         /* bug or odd chess variant */
5226         *fromX = currentMoveString[0] - AAA;
5227         *fromY = currentMoveString[1] - ONE;
5228         *toX = currentMoveString[2] - AAA;
5229         *toY = currentMoveString[3] - ONE;
5230         *promoChar = currentMoveString[4];
5231         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5232             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5233     if (appData.debugMode) {
5234         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5235     }
5236             *fromX = *fromY = *toX = *toY = 0;
5237             return FALSE;
5238         }
5239         if (appData.testLegality) {
5240           return (*moveType != IllegalMove);
5241         } else {
5242           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5243                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5244         }
5245
5246       case WhiteDrop:
5247       case BlackDrop:
5248         *fromX = *moveType == WhiteDrop ?
5249           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5250           (int) CharToPiece(ToLower(currentMoveString[0]));
5251         *fromY = DROP_RANK;
5252         *toX = currentMoveString[2] - AAA;
5253         *toY = currentMoveString[3] - ONE;
5254         *promoChar = NULLCHAR;
5255         return TRUE;
5256
5257       case AmbiguousMove:
5258       case ImpossibleMove:
5259       case EndOfFile:
5260       case ElapsedTime:
5261       case Comment:
5262       case PGNTag:
5263       case NAG:
5264       case WhiteWins:
5265       case BlackWins:
5266       case GameIsDrawn:
5267       default:
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5270     }
5271         /* bug? */
5272         *fromX = *fromY = *toX = *toY = 0;
5273         *promoChar = NULLCHAR;
5274         return FALSE;
5275     }
5276 }
5277
5278 Boolean pushed = FALSE;
5279
5280 void
5281 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5282 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5283   int fromX, fromY, toX, toY; char promoChar;
5284   ChessMove moveType;
5285   Boolean valid;
5286   int nr = 0;
5287
5288   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5289     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5290     pushed = TRUE;
5291   }
5292   endPV = forwardMostMove;
5293   do {
5294     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5295     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5296     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5297 if(appData.debugMode){
5298 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);
5299 }
5300     if(!valid && nr == 0 &&
5301        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5302         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5303         // Hande case where played move is different from leading PV move
5304         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5305         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5306         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5307         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5308           endPV += 2; // if position different, keep this
5309           moveList[endPV-1][0] = fromX + AAA;
5310           moveList[endPV-1][1] = fromY + ONE;
5311           moveList[endPV-1][2] = toX + AAA;
5312           moveList[endPV-1][3] = toY + ONE;
5313           parseList[endPV-1][0] = NULLCHAR;
5314           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5315         }
5316       }
5317     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5318     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5319     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5320     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5321         valid++; // allow comments in PV
5322         continue;
5323     }
5324     nr++;
5325     if(endPV+1 > framePtr) break; // no space, truncate
5326     if(!valid) break;
5327     endPV++;
5328     CopyBoard(boards[endPV], boards[endPV-1]);
5329     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5330     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5331     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5332     CoordsToAlgebraic(boards[endPV - 1],
5333                              PosFlags(endPV - 1),
5334                              fromY, fromX, toY, toX, promoChar,
5335                              parseList[endPV - 1]);
5336   } while(valid);
5337   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5338   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5339   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5340                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5341   DrawPosition(TRUE, boards[currentMove]);
5342 }
5343
5344 int
5345 MultiPV(ChessProgramState *cps)
5346 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5347         int i;
5348         for(i=0; i<cps->nrOptions; i++)
5349             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5350                 return i;
5351         return -1;
5352 }
5353
5354 Boolean
5355 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5356 {
5357         int startPV, multi, lineStart, origIndex = index;
5358         char *p, buf2[MSG_SIZ];
5359
5360         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5361         lastX = x; lastY = y;
5362         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5363         lineStart = startPV = index;
5364         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5365         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5366         index = startPV;
5367         do{ while(buf[index] && buf[index] != '\n') index++;
5368         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5369         buf[index] = 0;
5370         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5371                 int n = first.option[multi].value;
5372                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5373                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5374                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5375                 first.option[multi].value = n;
5376                 *start = *end = 0;
5377                 return FALSE;
5378         }
5379         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5380         *start = startPV; *end = index-1;
5381         return TRUE;
5382 }
5383
5384 Boolean
5385 LoadPV(int x, int y)
5386 { // called on right mouse click to load PV
5387   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5388   lastX = x; lastY = y;
5389   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5390   return TRUE;
5391 }
5392
5393 void
5394 UnLoadPV()
5395 {
5396   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5397   if(endPV < 0) return;
5398   endPV = -1;
5399   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5400         Boolean saveAnimate = appData.animate;
5401         if(pushed) {
5402             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5403                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5404             } else storedGames--; // abandon shelved tail of original game
5405         }
5406         pushed = FALSE;
5407         forwardMostMove = currentMove;
5408         currentMove = oldFMM;
5409         appData.animate = FALSE;
5410         ToNrEvent(forwardMostMove);
5411         appData.animate = saveAnimate;
5412   }
5413   currentMove = forwardMostMove;
5414   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5415   ClearPremoveHighlights();
5416   DrawPosition(TRUE, boards[currentMove]);
5417 }
5418
5419 void
5420 MovePV(int x, int y, int h)
5421 { // step through PV based on mouse coordinates (called on mouse move)
5422   int margin = h>>3, step = 0;
5423
5424   // we must somehow check if right button is still down (might be released off board!)
5425   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5426   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5427   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5428   if(!step) return;
5429   lastX = x; lastY = y;
5430
5431   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5432   if(endPV < 0) return;
5433   if(y < margin) step = 1; else
5434   if(y > h - margin) step = -1;
5435   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5436   currentMove += step;
5437   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5438   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5439                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5440   DrawPosition(FALSE, boards[currentMove]);
5441 }
5442
5443
5444 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5445 // All positions will have equal probability, but the current method will not provide a unique
5446 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5447 #define DARK 1
5448 #define LITE 2
5449 #define ANY 3
5450
5451 int squaresLeft[4];
5452 int piecesLeft[(int)BlackPawn];
5453 int seed, nrOfShuffles;
5454
5455 void GetPositionNumber()
5456 {       // sets global variable seed
5457         int i;
5458
5459         seed = appData.defaultFrcPosition;
5460         if(seed < 0) { // randomize based on time for negative FRC position numbers
5461                 for(i=0; i<50; i++) seed += random();
5462                 seed = random() ^ random() >> 8 ^ random() << 8;
5463                 if(seed<0) seed = -seed;
5464         }
5465 }
5466
5467 int put(Board board, int pieceType, int rank, int n, int shade)
5468 // put the piece on the (n-1)-th empty squares of the given shade
5469 {
5470         int i;
5471
5472         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5473                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5474                         board[rank][i] = (ChessSquare) pieceType;
5475                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5476                         squaresLeft[ANY]--;
5477                         piecesLeft[pieceType]--;
5478                         return i;
5479                 }
5480         }
5481         return -1;
5482 }
5483
5484
5485 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5486 // calculate where the next piece goes, (any empty square), and put it there
5487 {
5488         int i;
5489
5490         i = seed % squaresLeft[shade];
5491         nrOfShuffles *= squaresLeft[shade];
5492         seed /= squaresLeft[shade];
5493         put(board, pieceType, rank, i, shade);
5494 }
5495
5496 void AddTwoPieces(Board board, int pieceType, int rank)
5497 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5498 {
5499         int i, n=squaresLeft[ANY], j=n-1, k;
5500
5501         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5502         i = seed % k;  // pick one
5503         nrOfShuffles *= k;
5504         seed /= k;
5505         while(i >= j) i -= j--;
5506         j = n - 1 - j; i += j;
5507         put(board, pieceType, rank, j, ANY);
5508         put(board, pieceType, rank, i, ANY);
5509 }
5510
5511 void SetUpShuffle(Board board, int number)
5512 {
5513         int i, p, first=1;
5514
5515         GetPositionNumber(); nrOfShuffles = 1;
5516
5517         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5518         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5519         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5520
5521         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5522
5523         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5524             p = (int) board[0][i];
5525             if(p < (int) BlackPawn) piecesLeft[p] ++;
5526             board[0][i] = EmptySquare;
5527         }
5528
5529         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5530             // shuffles restricted to allow normal castling put KRR first
5531             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5532                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5533             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5534                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5535             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5536                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5537             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5538                 put(board, WhiteRook, 0, 0, ANY);
5539             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5540         }
5541
5542         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5543             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5544             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5545                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5546                 while(piecesLeft[p] >= 2) {
5547                     AddOnePiece(board, p, 0, LITE);
5548                     AddOnePiece(board, p, 0, DARK);
5549                 }
5550                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5551             }
5552
5553         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5554             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5555             // but we leave King and Rooks for last, to possibly obey FRC restriction
5556             if(p == (int)WhiteRook) continue;
5557             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5558             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5559         }
5560
5561         // now everything is placed, except perhaps King (Unicorn) and Rooks
5562
5563         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5564             // Last King gets castling rights
5565             while(piecesLeft[(int)WhiteUnicorn]) {
5566                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5567                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5568             }
5569
5570             while(piecesLeft[(int)WhiteKing]) {
5571                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5572                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5573             }
5574
5575
5576         } else {
5577             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5578             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5579         }
5580
5581         // Only Rooks can be left; simply place them all
5582         while(piecesLeft[(int)WhiteRook]) {
5583                 i = put(board, WhiteRook, 0, 0, ANY);
5584                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5585                         if(first) {
5586                                 first=0;
5587                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5588                         }
5589                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5590                 }
5591         }
5592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5593             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5594         }
5595
5596         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5597 }
5598
5599 int SetCharTable( char *table, const char * map )
5600 /* [HGM] moved here from winboard.c because of its general usefulness */
5601 /*       Basically a safe strcpy that uses the last character as King */
5602 {
5603     int result = FALSE; int NrPieces;
5604
5605     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5606                     && NrPieces >= 12 && !(NrPieces&1)) {
5607         int i; /* [HGM] Accept even length from 12 to 34 */
5608
5609         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5610         for( i=0; i<NrPieces/2-1; i++ ) {
5611             table[i] = map[i];
5612             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5613         }
5614         table[(int) WhiteKing]  = map[NrPieces/2-1];
5615         table[(int) BlackKing]  = map[NrPieces-1];
5616
5617         result = TRUE;
5618     }
5619
5620     return result;
5621 }
5622
5623 void Prelude(Board board)
5624 {       // [HGM] superchess: random selection of exo-pieces
5625         int i, j, k; ChessSquare p;
5626         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5627
5628         GetPositionNumber(); // use FRC position number
5629
5630         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5631             SetCharTable(pieceToChar, appData.pieceToCharTable);
5632             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5633                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5634         }
5635
5636         j = seed%4;                 seed /= 4;
5637         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5638         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5639         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5640         j = seed%3 + (seed%3 >= j); seed /= 3;
5641         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5642         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5643         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5644         j = seed%3;                 seed /= 3;
5645         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5646         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5647         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5648         j = seed%2 + (seed%2 >= j); seed /= 2;
5649         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5650         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5651         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5652         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5653         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5654         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5655         put(board, exoPieces[0],    0, 0, ANY);
5656         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5657 }
5658
5659 void
5660 InitPosition(redraw)
5661      int redraw;
5662 {
5663     ChessSquare (* pieces)[BOARD_FILES];
5664     int i, j, pawnRow, overrule,
5665     oldx = gameInfo.boardWidth,
5666     oldy = gameInfo.boardHeight,
5667     oldh = gameInfo.holdingsWidth;
5668     static int oldv;
5669
5670     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5671
5672     /* [AS] Initialize pv info list [HGM] and game status */
5673     {
5674         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5675             pvInfoList[i].depth = 0;
5676             boards[i][EP_STATUS] = EP_NONE;
5677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5678         }
5679
5680         initialRulePlies = 0; /* 50-move counter start */
5681
5682         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5683         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5684     }
5685
5686
5687     /* [HGM] logic here is completely changed. In stead of full positions */
5688     /* the initialized data only consist of the two backranks. The switch */
5689     /* selects which one we will use, which is than copied to the Board   */
5690     /* initialPosition, which for the rest is initialized by Pawns and    */
5691     /* empty squares. This initial position is then copied to boards[0],  */
5692     /* possibly after shuffling, so that it remains available.            */
5693
5694     gameInfo.holdingsWidth = 0; /* default board sizes */
5695     gameInfo.boardWidth    = 8;
5696     gameInfo.boardHeight   = 8;
5697     gameInfo.holdingsSize  = 0;
5698     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5699     for(i=0; i<BOARD_FILES-2; i++)
5700       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5701     initialPosition[EP_STATUS] = EP_NONE;
5702     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5703     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5704          SetCharTable(pieceNickName, appData.pieceNickNames);
5705     else SetCharTable(pieceNickName, "............");
5706     pieces = FIDEArray;
5707
5708     switch (gameInfo.variant) {
5709     case VariantFischeRandom:
5710       shuffleOpenings = TRUE;
5711     default:
5712       break;
5713     case VariantShatranj:
5714       pieces = ShatranjArray;
5715       nrCastlingRights = 0;
5716       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5717       break;
5718     case VariantMakruk:
5719       pieces = makrukArray;
5720       nrCastlingRights = 0;
5721       startedFromSetupPosition = TRUE;
5722       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5723       break;
5724     case VariantTwoKings:
5725       pieces = twoKingsArray;
5726       break;
5727     case VariantCapaRandom:
5728       shuffleOpenings = TRUE;
5729     case VariantCapablanca:
5730       pieces = CapablancaArray;
5731       gameInfo.boardWidth = 10;
5732       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5733       break;
5734     case VariantGothic:
5735       pieces = GothicArray;
5736       gameInfo.boardWidth = 10;
5737       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5738       break;
5739     case VariantSChess:
5740       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5741       gameInfo.holdingsSize = 7;
5742       break;
5743     case VariantJanus:
5744       pieces = JanusArray;
5745       gameInfo.boardWidth = 10;
5746       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5747       nrCastlingRights = 6;
5748         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5749         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5750         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5751         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5752         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5753         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5754       break;
5755     case VariantFalcon:
5756       pieces = FalconArray;
5757       gameInfo.boardWidth = 10;
5758       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5759       break;
5760     case VariantXiangqi:
5761       pieces = XiangqiArray;
5762       gameInfo.boardWidth  = 9;
5763       gameInfo.boardHeight = 10;
5764       nrCastlingRights = 0;
5765       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5766       break;
5767     case VariantShogi:
5768       pieces = ShogiArray;
5769       gameInfo.boardWidth  = 9;
5770       gameInfo.boardHeight = 9;
5771       gameInfo.holdingsSize = 7;
5772       nrCastlingRights = 0;
5773       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5774       break;
5775     case VariantCourier:
5776       pieces = CourierArray;
5777       gameInfo.boardWidth  = 12;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5780       break;
5781     case VariantKnightmate:
5782       pieces = KnightmateArray;
5783       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5784       break;
5785     case VariantSpartan:
5786       pieces = SpartanArray;
5787       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5788       break;
5789     case VariantFairy:
5790       pieces = fairyArray;
5791       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5792       break;
5793     case VariantGreat:
5794       pieces = GreatArray;
5795       gameInfo.boardWidth = 10;
5796       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5797       gameInfo.holdingsSize = 8;
5798       break;
5799     case VariantSuper:
5800       pieces = FIDEArray;
5801       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5802       gameInfo.holdingsSize = 8;
5803       startedFromSetupPosition = TRUE;
5804       break;
5805     case VariantCrazyhouse:
5806     case VariantBughouse:
5807       pieces = FIDEArray;
5808       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5809       gameInfo.holdingsSize = 5;
5810       break;
5811     case VariantWildCastle:
5812       pieces = FIDEArray;
5813       /* !!?shuffle with kings guaranteed to be on d or e file */
5814       shuffleOpenings = 1;
5815       break;
5816     case VariantNoCastle:
5817       pieces = FIDEArray;
5818       nrCastlingRights = 0;
5819       /* !!?unconstrained back-rank shuffle */
5820       shuffleOpenings = 1;
5821       break;
5822     }
5823
5824     overrule = 0;
5825     if(appData.NrFiles >= 0) {
5826         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5827         gameInfo.boardWidth = appData.NrFiles;
5828     }
5829     if(appData.NrRanks >= 0) {
5830         gameInfo.boardHeight = appData.NrRanks;
5831     }
5832     if(appData.holdingsSize >= 0) {
5833         i = appData.holdingsSize;
5834         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5835         gameInfo.holdingsSize = i;
5836     }
5837     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5838     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5839         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5840
5841     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5842     if(pawnRow < 1) pawnRow = 1;
5843     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5844
5845     /* User pieceToChar list overrules defaults */
5846     if(appData.pieceToCharTable != NULL)
5847         SetCharTable(pieceToChar, appData.pieceToCharTable);
5848
5849     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5850
5851         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5852             s = (ChessSquare) 0; /* account holding counts in guard band */
5853         for( i=0; i<BOARD_HEIGHT; i++ )
5854             initialPosition[i][j] = s;
5855
5856         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5857         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5858         initialPosition[pawnRow][j] = WhitePawn;
5859         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5860         if(gameInfo.variant == VariantXiangqi) {
5861             if(j&1) {
5862                 initialPosition[pawnRow][j] =
5863                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5864                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5865                    initialPosition[2][j] = WhiteCannon;
5866                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5867                 }
5868             }
5869         }
5870         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5871     }
5872     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5873
5874             j=BOARD_LEFT+1;
5875             initialPosition[1][j] = WhiteBishop;
5876             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5877             j=BOARD_RGHT-2;
5878             initialPosition[1][j] = WhiteRook;
5879             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5880     }
5881
5882     if( nrCastlingRights == -1) {
5883         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5884         /*       This sets default castling rights from none to normal corners   */
5885         /* Variants with other castling rights must set them themselves above    */
5886         nrCastlingRights = 6;
5887
5888         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5889         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5890         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5891         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5892         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5893         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5894      }
5895
5896      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5897      if(gameInfo.variant == VariantGreat) { // promotion commoners
5898         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5899         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5900         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5901         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5902      }
5903      if( gameInfo.variant == VariantSChess ) {
5904       initialPosition[1][0] = BlackMarshall;
5905       initialPosition[2][0] = BlackAngel;
5906       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5907       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5908       initialPosition[1][1] = initialPosition[2][1] = 
5909       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5910      }
5911   if (appData.debugMode) {
5912     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5913   }
5914     if(shuffleOpenings) {
5915         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5916         startedFromSetupPosition = TRUE;
5917     }
5918     if(startedFromPositionFile) {
5919       /* [HGM] loadPos: use PositionFile for every new game */
5920       CopyBoard(initialPosition, filePosition);
5921       for(i=0; i<nrCastlingRights; i++)
5922           initialRights[i] = filePosition[CASTLING][i];
5923       startedFromSetupPosition = TRUE;
5924     }
5925
5926     CopyBoard(boards[0], initialPosition);
5927
5928     if(oldx != gameInfo.boardWidth ||
5929        oldy != gameInfo.boardHeight ||
5930        oldv != gameInfo.variant ||
5931        oldh != gameInfo.holdingsWidth
5932                                          )
5933             InitDrawingSizes(-2 ,0);
5934
5935     oldv = gameInfo.variant;
5936     if (redraw)
5937       DrawPosition(TRUE, boards[currentMove]);
5938 }
5939
5940 void
5941 SendBoard(cps, moveNum)
5942      ChessProgramState *cps;
5943      int moveNum;
5944 {
5945     char message[MSG_SIZ];
5946
5947     if (cps->useSetboard) {
5948       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5949       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5950       SendToProgram(message, cps);
5951       free(fen);
5952
5953     } else {
5954       ChessSquare *bp;
5955       int i, j;
5956       /* Kludge to set black to move, avoiding the troublesome and now
5957        * deprecated "black" command.
5958        */
5959       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5960         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5961
5962       SendToProgram("edit\n", cps);
5963       SendToProgram("#\n", cps);
5964       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5965         bp = &boards[moveNum][i][BOARD_LEFT];
5966         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5967           if ((int) *bp < (int) BlackPawn) {
5968             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5969                     AAA + j, ONE + i);
5970             if(message[0] == '+' || message[0] == '~') {
5971               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5972                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5973                         AAA + j, ONE + i);
5974             }
5975             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5976                 message[1] = BOARD_RGHT   - 1 - j + '1';
5977                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5978             }
5979             SendToProgram(message, cps);
5980           }
5981         }
5982       }
5983
5984       SendToProgram("c\n", cps);
5985       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5986         bp = &boards[moveNum][i][BOARD_LEFT];
5987         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5988           if (((int) *bp != (int) EmptySquare)
5989               && ((int) *bp >= (int) BlackPawn)) {
5990             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5991                     AAA + j, ONE + i);
5992             if(message[0] == '+' || message[0] == '~') {
5993               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5994                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5995                         AAA + j, ONE + i);
5996             }
5997             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5998                 message[1] = BOARD_RGHT   - 1 - j + '1';
5999                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6000             }
6001             SendToProgram(message, cps);
6002           }
6003         }
6004       }
6005
6006       SendToProgram(".\n", cps);
6007     }
6008     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6009 }
6010
6011 ChessSquare
6012 DefaultPromoChoice(int white)
6013 {
6014     ChessSquare result;
6015     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6016         result = WhiteFerz; // no choice
6017     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6018         result= WhiteKing; // in Suicide Q is the last thing we want
6019     else if(gameInfo.variant == VariantSpartan)
6020         result = white ? WhiteQueen : WhiteAngel;
6021     else result = WhiteQueen;
6022     if(!white) result = WHITE_TO_BLACK result;
6023     return result;
6024 }
6025
6026 static int autoQueen; // [HGM] oneclick
6027
6028 int
6029 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6030 {
6031     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6032     /* [HGM] add Shogi promotions */
6033     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6034     ChessSquare piece;
6035     ChessMove moveType;
6036     Boolean premove;
6037
6038     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6039     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6040
6041     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6042       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6043         return FALSE;
6044
6045     piece = boards[currentMove][fromY][fromX];
6046     if(gameInfo.variant == VariantShogi) {
6047         promotionZoneSize = BOARD_HEIGHT/3;
6048         highestPromotingPiece = (int)WhiteFerz;
6049     } else if(gameInfo.variant == VariantMakruk) {
6050         promotionZoneSize = 3;
6051     }
6052
6053     // Treat Lance as Pawn when it is not representing Amazon
6054     if(gameInfo.variant != VariantSuper) {
6055         if(piece == WhiteLance) piece = WhitePawn; else
6056         if(piece == BlackLance) piece = BlackPawn;
6057     }
6058
6059     // next weed out all moves that do not touch the promotion zone at all
6060     if((int)piece >= BlackPawn) {
6061         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6062              return FALSE;
6063         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6064     } else {
6065         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6066            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6067     }
6068
6069     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6070
6071     // weed out mandatory Shogi promotions
6072     if(gameInfo.variant == VariantShogi) {
6073         if(piece >= BlackPawn) {
6074             if(toY == 0 && piece == BlackPawn ||
6075                toY == 0 && piece == BlackQueen ||
6076                toY <= 1 && piece == BlackKnight) {
6077                 *promoChoice = '+';
6078                 return FALSE;
6079             }
6080         } else {
6081             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6082                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6083                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6084                 *promoChoice = '+';
6085                 return FALSE;
6086             }
6087         }
6088     }
6089
6090     // weed out obviously illegal Pawn moves
6091     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6092         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6093         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6094         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6095         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6096         // note we are not allowed to test for valid (non-)capture, due to premove
6097     }
6098
6099     // we either have a choice what to promote to, or (in Shogi) whether to promote
6100     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6101         *promoChoice = PieceToChar(BlackFerz);  // no choice
6102         return FALSE;
6103     }
6104     // no sense asking what we must promote to if it is going to explode...
6105     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6106         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6107         return FALSE;
6108     }
6109     // give caller the default choice even if we will not make it
6110     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6111     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6112     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6113                            && gameInfo.variant != VariantShogi
6114                            && gameInfo.variant != VariantSuper) return FALSE;
6115     if(autoQueen) return FALSE; // predetermined
6116
6117     // suppress promotion popup on illegal moves that are not premoves
6118     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6119               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6120     if(appData.testLegality && !premove) {
6121         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6122                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6123         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6124             return FALSE;
6125     }
6126
6127     return TRUE;
6128 }
6129
6130 int
6131 InPalace(row, column)
6132      int row, column;
6133 {   /* [HGM] for Xiangqi */
6134     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6135          column < (BOARD_WIDTH + 4)/2 &&
6136          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6137     return FALSE;
6138 }
6139
6140 int
6141 PieceForSquare (x, y)
6142      int x;
6143      int y;
6144 {
6145   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6146      return -1;
6147   else
6148      return boards[currentMove][y][x];
6149 }
6150
6151 int
6152 OKToStartUserMove(x, y)
6153      int x, y;
6154 {
6155     ChessSquare from_piece;
6156     int white_piece;
6157
6158     if (matchMode) return FALSE;
6159     if (gameMode == EditPosition) return TRUE;
6160
6161     if (x >= 0 && y >= 0)
6162       from_piece = boards[currentMove][y][x];
6163     else
6164       from_piece = EmptySquare;
6165
6166     if (from_piece == EmptySquare) return FALSE;
6167
6168     white_piece = (int)from_piece >= (int)WhitePawn &&
6169       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6170
6171     switch (gameMode) {
6172       case PlayFromGameFile:
6173       case AnalyzeFile:
6174       case TwoMachinesPlay:
6175       case EndOfGame:
6176         return FALSE;
6177
6178       case IcsObserving:
6179       case IcsIdle:
6180         return FALSE;
6181
6182       case MachinePlaysWhite:
6183       case IcsPlayingBlack:
6184         if (appData.zippyPlay) return FALSE;
6185         if (white_piece) {
6186             DisplayMoveError(_("You are playing Black"));
6187             return FALSE;
6188         }
6189         break;
6190
6191       case MachinePlaysBlack:
6192       case IcsPlayingWhite:
6193         if (appData.zippyPlay) return FALSE;
6194         if (!white_piece) {
6195             DisplayMoveError(_("You are playing White"));
6196             return FALSE;
6197         }
6198         break;
6199
6200       case EditGame:
6201         if (!white_piece && WhiteOnMove(currentMove)) {
6202             DisplayMoveError(_("It is White's turn"));
6203             return FALSE;
6204         }
6205         if (white_piece && !WhiteOnMove(currentMove)) {
6206             DisplayMoveError(_("It is Black's turn"));
6207             return FALSE;
6208         }
6209         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6210             /* Editing correspondence game history */
6211             /* Could disallow this or prompt for confirmation */
6212             cmailOldMove = -1;
6213         }
6214         break;
6215
6216       case BeginningOfGame:
6217         if (appData.icsActive) return FALSE;
6218         if (!appData.noChessProgram) {
6219             if (!white_piece) {
6220                 DisplayMoveError(_("You are playing White"));
6221                 return FALSE;
6222             }
6223         }
6224         break;
6225
6226       case Training:
6227         if (!white_piece && WhiteOnMove(currentMove)) {
6228             DisplayMoveError(_("It is White's turn"));
6229             return FALSE;
6230         }
6231         if (white_piece && !WhiteOnMove(currentMove)) {
6232             DisplayMoveError(_("It is Black's turn"));
6233             return FALSE;
6234         }
6235         break;
6236
6237       default:
6238       case IcsExamining:
6239         break;
6240     }
6241     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6242         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6243         && gameMode != AnalyzeFile && gameMode != Training) {
6244         DisplayMoveError(_("Displayed position is not current"));
6245         return FALSE;
6246     }
6247     return TRUE;
6248 }
6249
6250 Boolean
6251 OnlyMove(int *x, int *y, Boolean captures) {
6252     DisambiguateClosure cl;
6253     if (appData.zippyPlay) return FALSE;
6254     switch(gameMode) {
6255       case MachinePlaysBlack:
6256       case IcsPlayingWhite:
6257       case BeginningOfGame:
6258         if(!WhiteOnMove(currentMove)) return FALSE;
6259         break;
6260       case MachinePlaysWhite:
6261       case IcsPlayingBlack:
6262         if(WhiteOnMove(currentMove)) return FALSE;
6263         break;
6264       case EditGame:
6265         break;
6266       default:
6267         return FALSE;
6268     }
6269     cl.pieceIn = EmptySquare;
6270     cl.rfIn = *y;
6271     cl.ffIn = *x;
6272     cl.rtIn = -1;
6273     cl.ftIn = -1;
6274     cl.promoCharIn = NULLCHAR;
6275     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6276     if( cl.kind == NormalMove ||
6277         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6278         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6279         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6280       fromX = cl.ff;
6281       fromY = cl.rf;
6282       *x = cl.ft;
6283       *y = cl.rt;
6284       return TRUE;
6285     }
6286     if(cl.kind != ImpossibleMove) return FALSE;
6287     cl.pieceIn = EmptySquare;
6288     cl.rfIn = -1;
6289     cl.ffIn = -1;
6290     cl.rtIn = *y;
6291     cl.ftIn = *x;
6292     cl.promoCharIn = NULLCHAR;
6293     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6294     if( cl.kind == NormalMove ||
6295         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6296         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6297         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6298       fromX = cl.ff;
6299       fromY = cl.rf;
6300       *x = cl.ft;
6301       *y = cl.rt;
6302       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6303       return TRUE;
6304     }
6305     return FALSE;
6306 }
6307
6308 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6309 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6310 int lastLoadGameUseList = FALSE;
6311 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6312 ChessMove lastLoadGameStart = EndOfFile;
6313
6314 void
6315 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6316      int fromX, fromY, toX, toY;
6317      int promoChar;
6318 {
6319     ChessMove moveType;
6320     ChessSquare pdown, pup;
6321
6322     /* Check if the user is playing in turn.  This is complicated because we
6323        let the user "pick up" a piece before it is his turn.  So the piece he
6324        tried to pick up may have been captured by the time he puts it down!
6325        Therefore we use the color the user is supposed to be playing in this
6326        test, not the color of the piece that is currently on the starting
6327        square---except in EditGame mode, where the user is playing both
6328        sides; fortunately there the capture race can't happen.  (It can
6329        now happen in IcsExamining mode, but that's just too bad.  The user
6330        will get a somewhat confusing message in that case.)
6331        */
6332
6333     switch (gameMode) {
6334       case PlayFromGameFile:
6335       case AnalyzeFile:
6336       case TwoMachinesPlay:
6337       case EndOfGame:
6338       case IcsObserving:
6339       case IcsIdle:
6340         /* We switched into a game mode where moves are not accepted,
6341            perhaps while the mouse button was down. */
6342         return;
6343
6344       case MachinePlaysWhite:
6345         /* User is moving for Black */
6346         if (WhiteOnMove(currentMove)) {
6347             DisplayMoveError(_("It is White's turn"));
6348             return;
6349         }
6350         break;
6351
6352       case MachinePlaysBlack:
6353         /* User is moving for White */
6354         if (!WhiteOnMove(currentMove)) {
6355             DisplayMoveError(_("It is Black's turn"));
6356             return;
6357         }
6358         break;
6359
6360       case EditGame:
6361       case IcsExamining:
6362       case BeginningOfGame:
6363       case AnalyzeMode:
6364       case Training:
6365         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6366         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6367             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6368             /* User is moving for Black */
6369             if (WhiteOnMove(currentMove)) {
6370                 DisplayMoveError(_("It is White's turn"));
6371                 return;
6372             }
6373         } else {
6374             /* User is moving for White */
6375             if (!WhiteOnMove(currentMove)) {
6376                 DisplayMoveError(_("It is Black's turn"));
6377                 return;
6378             }
6379         }
6380         break;
6381
6382       case IcsPlayingBlack:
6383         /* User is moving for Black */
6384         if (WhiteOnMove(currentMove)) {
6385             if (!appData.premove) {
6386                 DisplayMoveError(_("It is White's turn"));
6387             } else if (toX >= 0 && toY >= 0) {
6388                 premoveToX = toX;
6389                 premoveToY = toY;
6390                 premoveFromX = fromX;
6391                 premoveFromY = fromY;
6392                 premovePromoChar = promoChar;
6393                 gotPremove = 1;
6394                 if (appData.debugMode)
6395                     fprintf(debugFP, "Got premove: fromX %d,"
6396                             "fromY %d, toX %d, toY %d\n",
6397                             fromX, fromY, toX, toY);
6398             }
6399             return;
6400         }
6401         break;
6402
6403       case IcsPlayingWhite:
6404         /* User is moving for White */
6405         if (!WhiteOnMove(currentMove)) {
6406             if (!appData.premove) {
6407                 DisplayMoveError(_("It is Black's turn"));
6408             } else if (toX >= 0 && toY >= 0) {
6409                 premoveToX = toX;
6410                 premoveToY = toY;
6411                 premoveFromX = fromX;
6412                 premoveFromY = fromY;
6413                 premovePromoChar = promoChar;
6414                 gotPremove = 1;
6415                 if (appData.debugMode)
6416                     fprintf(debugFP, "Got premove: fromX %d,"
6417                             "fromY %d, toX %d, toY %d\n",
6418                             fromX, fromY, toX, toY);
6419             }
6420             return;
6421         }
6422         break;
6423
6424       default:
6425         break;
6426
6427       case EditPosition:
6428         /* EditPosition, empty square, or different color piece;
6429            click-click move is possible */
6430         if (toX == -2 || toY == -2) {
6431             boards[0][fromY][fromX] = EmptySquare;
6432             DrawPosition(FALSE, boards[currentMove]);
6433             return;
6434         } else if (toX >= 0 && toY >= 0) {
6435             boards[0][toY][toX] = boards[0][fromY][fromX];
6436             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6437                 if(boards[0][fromY][0] != EmptySquare) {
6438                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6439                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6440                 }
6441             } else
6442             if(fromX == BOARD_RGHT+1) {
6443                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6444                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6445                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6446                 }
6447             } else
6448             boards[0][fromY][fromX] = EmptySquare;
6449             DrawPosition(FALSE, boards[currentMove]);
6450             return;
6451         }
6452         return;
6453     }
6454
6455     if(toX < 0 || toY < 0) return;
6456     pdown = boards[currentMove][fromY][fromX];
6457     pup = boards[currentMove][toY][toX];
6458
6459     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6460     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6461          if( pup != EmptySquare ) return;
6462          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6463            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6464                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6465            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6466            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6467            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6468            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6469          fromY = DROP_RANK;
6470     }
6471
6472     /* [HGM] always test for legality, to get promotion info */
6473     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6474                                          fromY, fromX, toY, toX, promoChar);
6475     /* [HGM] but possibly ignore an IllegalMove result */
6476     if (appData.testLegality) {
6477         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6478             DisplayMoveError(_("Illegal move"));
6479             return;
6480         }
6481     }
6482
6483     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6484 }
6485
6486 /* Common tail of UserMoveEvent and DropMenuEvent */
6487 int
6488 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6489      ChessMove moveType;
6490      int fromX, fromY, toX, toY;
6491      /*char*/int promoChar;
6492 {
6493     char *bookHit = 0;
6494
6495     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6496         // [HGM] superchess: suppress promotions to non-available piece
6497         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6498         if(WhiteOnMove(currentMove)) {
6499             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6500         } else {
6501             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6502         }
6503     }
6504
6505     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6506        move type in caller when we know the move is a legal promotion */
6507     if(moveType == NormalMove && promoChar)
6508         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6509
6510     /* [HGM] <popupFix> The following if has been moved here from
6511        UserMoveEvent(). Because it seemed to belong here (why not allow
6512        piece drops in training games?), and because it can only be
6513        performed after it is known to what we promote. */
6514     if (gameMode == Training) {
6515       /* compare the move played on the board to the next move in the
6516        * game. If they match, display the move and the opponent's response.
6517        * If they don't match, display an error message.
6518        */
6519       int saveAnimate;
6520       Board testBoard;
6521       CopyBoard(testBoard, boards[currentMove]);
6522       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6523
6524       if (CompareBoards(testBoard, boards[currentMove+1])) {
6525         ForwardInner(currentMove+1);
6526
6527         /* Autoplay the opponent's response.
6528          * if appData.animate was TRUE when Training mode was entered,
6529          * the response will be animated.
6530          */
6531         saveAnimate = appData.animate;
6532         appData.animate = animateTraining;
6533         ForwardInner(currentMove+1);
6534         appData.animate = saveAnimate;
6535
6536         /* check for the end of the game */
6537         if (currentMove >= forwardMostMove) {
6538           gameMode = PlayFromGameFile;
6539           ModeHighlight();
6540           SetTrainingModeOff();
6541           DisplayInformation(_("End of game"));
6542         }
6543       } else {
6544         DisplayError(_("Incorrect move"), 0);
6545       }
6546       return 1;
6547     }
6548
6549   /* Ok, now we know that the move is good, so we can kill
6550      the previous line in Analysis Mode */
6551   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6552                                 && currentMove < forwardMostMove) {
6553     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6554     else forwardMostMove = currentMove;
6555   }
6556
6557   /* If we need the chess program but it's dead, restart it */
6558   ResurrectChessProgram();
6559
6560   /* A user move restarts a paused game*/
6561   if (pausing)
6562     PauseEvent();
6563
6564   thinkOutput[0] = NULLCHAR;
6565
6566   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6567
6568   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6569     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6570     return 1;
6571   }
6572
6573   if (gameMode == BeginningOfGame) {
6574     if (appData.noChessProgram) {
6575       gameMode = EditGame;
6576       SetGameInfo();
6577     } else {
6578       char buf[MSG_SIZ];
6579       gameMode = MachinePlaysBlack;
6580       StartClocks();
6581       SetGameInfo();
6582       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6583       DisplayTitle(buf);
6584       if (first.sendName) {
6585         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6586         SendToProgram(buf, &first);
6587       }
6588       StartClocks();
6589     }
6590     ModeHighlight();
6591   }
6592
6593   /* Relay move to ICS or chess engine */
6594   if (appData.icsActive) {
6595     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6596         gameMode == IcsExamining) {
6597       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6598         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6599         SendToICS("draw ");
6600         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6601       }
6602       // also send plain move, in case ICS does not understand atomic claims
6603       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6604       ics_user_moved = 1;
6605     }
6606   } else {
6607     if (first.sendTime && (gameMode == BeginningOfGame ||
6608                            gameMode == MachinePlaysWhite ||
6609                            gameMode == MachinePlaysBlack)) {
6610       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6611     }
6612     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6613          // [HGM] book: if program might be playing, let it use book
6614         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6615         first.maybeThinking = TRUE;
6616     } else SendMoveToProgram(forwardMostMove-1, &first);
6617     if (currentMove == cmailOldMove + 1) {
6618       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6619     }
6620   }
6621
6622   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6623
6624   switch (gameMode) {
6625   case EditGame:
6626     if(appData.testLegality)
6627     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6628     case MT_NONE:
6629     case MT_CHECK:
6630       break;
6631     case MT_CHECKMATE:
6632     case MT_STAINMATE:
6633       if (WhiteOnMove(currentMove)) {
6634         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6635       } else {
6636         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6637       }
6638       break;
6639     case MT_STALEMATE:
6640       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6641       break;
6642     }
6643     break;
6644
6645   case MachinePlaysBlack:
6646   case MachinePlaysWhite:
6647     /* disable certain menu options while machine is thinking */
6648     SetMachineThinkingEnables();
6649     break;
6650
6651   default:
6652     break;
6653   }
6654
6655   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6656   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6657
6658   if(bookHit) { // [HGM] book: simulate book reply
6659         static char bookMove[MSG_SIZ]; // a bit generous?
6660
6661         programStats.nodes = programStats.depth = programStats.time =
6662         programStats.score = programStats.got_only_move = 0;
6663         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6664
6665         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6666         strcat(bookMove, bookHit);
6667         HandleMachineMove(bookMove, &first);
6668   }
6669   return 1;
6670 }
6671
6672 void
6673 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6674      Board board;
6675      int flags;
6676      ChessMove kind;
6677      int rf, ff, rt, ft;
6678      VOIDSTAR closure;
6679 {
6680     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6681     Markers *m = (Markers *) closure;
6682     if(rf == fromY && ff == fromX)
6683         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6684                          || kind == WhiteCapturesEnPassant
6685                          || kind == BlackCapturesEnPassant);
6686     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6687 }
6688
6689 void
6690 MarkTargetSquares(int clear)
6691 {
6692   int x, y;
6693   if(!appData.markers || !appData.highlightDragging ||
6694      !appData.testLegality || gameMode == EditPosition) return;
6695   if(clear) {
6696     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6697   } else {
6698     int capt = 0;
6699     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6700     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6701       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6702       if(capt)
6703       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6704     }
6705   }
6706   DrawPosition(TRUE, NULL);
6707 }
6708
6709 int
6710 Explode(Board board, int fromX, int fromY, int toX, int toY)
6711 {
6712     if(gameInfo.variant == VariantAtomic &&
6713        (board[toY][toX] != EmptySquare ||                     // capture?
6714         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6715                          board[fromY][fromX] == BlackPawn   )
6716       )) {
6717         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6718         return TRUE;
6719     }
6720     return FALSE;
6721 }
6722
6723 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6724
6725 int CanPromote(ChessSquare piece, int y)
6726 {
6727         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6728         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6729         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6730            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6731            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6732                                                   gameInfo.variant == VariantMakruk) return FALSE;
6733         return (piece == BlackPawn && y == 1 ||
6734                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6735                 piece == BlackLance && y == 1 ||
6736                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6737 }
6738
6739 void LeftClick(ClickType clickType, int xPix, int yPix)
6740 {
6741     int x, y;
6742     Boolean saveAnimate;
6743     static int second = 0, promotionChoice = 0, clearFlag = 0;
6744     char promoChoice = NULLCHAR;
6745     ChessSquare piece;
6746
6747     if(appData.seekGraph && appData.icsActive && loggedOn &&
6748         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6749         SeekGraphClick(clickType, xPix, yPix, 0);
6750         return;
6751     }
6752
6753     if (clickType == Press) ErrorPopDown();
6754     MarkTargetSquares(1);
6755
6756     x = EventToSquare(xPix, BOARD_WIDTH);
6757     y = EventToSquare(yPix, BOARD_HEIGHT);
6758     if (!flipView && y >= 0) {
6759         y = BOARD_HEIGHT - 1 - y;
6760     }
6761     if (flipView && x >= 0) {
6762         x = BOARD_WIDTH - 1 - x;
6763     }
6764
6765     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6766         defaultPromoChoice = promoSweep;
6767         promoSweep = EmptySquare;   // terminate sweep
6768         promoDefaultAltered = TRUE;
6769         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6770     }
6771
6772     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6773         if(clickType == Release) return; // ignore upclick of click-click destination
6774         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6775         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6776         if(gameInfo.holdingsWidth &&
6777                 (WhiteOnMove(currentMove)
6778                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6779                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6780             // click in right holdings, for determining promotion piece
6781             ChessSquare p = boards[currentMove][y][x];
6782             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6783             if(p != EmptySquare) {
6784                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6785                 fromX = fromY = -1;
6786                 return;
6787             }
6788         }
6789         DrawPosition(FALSE, boards[currentMove]);
6790         return;
6791     }
6792
6793     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6794     if(clickType == Press
6795             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6796               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6797               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6798         return;
6799
6800     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6801         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6802
6803     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6804         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6805                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6806         defaultPromoChoice = DefaultPromoChoice(side);
6807     }
6808
6809     autoQueen = appData.alwaysPromoteToQueen;
6810
6811     if (fromX == -1) {
6812       int originalY = y;
6813       gatingPiece = EmptySquare;
6814       if (clickType != Press) {
6815         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6816             DragPieceEnd(xPix, yPix); dragging = 0;
6817             DrawPosition(FALSE, NULL);
6818         }
6819         return;
6820       }
6821       fromX = x; fromY = y;
6822       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6823          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6824          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6825             /* First square */
6826             if (OKToStartUserMove(fromX, fromY)) {
6827                 second = 0;
6828                 MarkTargetSquares(0);
6829                 DragPieceBegin(xPix, yPix); dragging = 1;
6830                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6831                     promoSweep = defaultPromoChoice;
6832                     selectFlag = 0; lastX = xPix; lastY = yPix;
6833                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6834                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6835                 }
6836                 if (appData.highlightDragging) {
6837                     SetHighlights(fromX, fromY, -1, -1);
6838                 }
6839             } else fromX = fromY = -1;
6840             return;
6841         }
6842     }
6843
6844     /* fromX != -1 */
6845     if (clickType == Press && gameMode != EditPosition) {
6846         ChessSquare fromP;
6847         ChessSquare toP;
6848         int frc;
6849
6850         // ignore off-board to clicks
6851         if(y < 0 || x < 0) return;
6852
6853         /* Check if clicking again on the same color piece */
6854         fromP = boards[currentMove][fromY][fromX];
6855         toP = boards[currentMove][y][x];
6856         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6857         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6858              WhitePawn <= toP && toP <= WhiteKing &&
6859              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6860              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6861             (BlackPawn <= fromP && fromP <= BlackKing &&
6862              BlackPawn <= toP && toP <= BlackKing &&
6863              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6864              !(fromP == BlackKing && toP == BlackRook && frc))) {
6865             /* Clicked again on same color piece -- changed his mind */
6866             second = (x == fromX && y == fromY);
6867             promoDefaultAltered = FALSE;
6868            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6869             if (appData.highlightDragging) {
6870                 SetHighlights(x, y, -1, -1);
6871             } else {
6872                 ClearHighlights();
6873             }
6874             if (OKToStartUserMove(x, y)) {
6875                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6876                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6877                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6878                  gatingPiece = boards[currentMove][fromY][fromX];
6879                 else gatingPiece = EmptySquare;
6880                 fromX = x;
6881                 fromY = y; dragging = 1;
6882                 MarkTargetSquares(0);
6883                 DragPieceBegin(xPix, yPix);
6884                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6885                     promoSweep = defaultPromoChoice;
6886                     selectFlag = 0; lastX = xPix; lastY = yPix;
6887                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6888                 }
6889             }
6890            }
6891            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6892            second = FALSE; 
6893         }
6894         // ignore clicks on holdings
6895         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6896     }
6897
6898     if (clickType == Release && x == fromX && y == fromY) {
6899         DragPieceEnd(xPix, yPix); dragging = 0;
6900         if(clearFlag) {
6901             // a deferred attempt to click-click move an empty square on top of a piece
6902             boards[currentMove][y][x] = EmptySquare;
6903             ClearHighlights();
6904             DrawPosition(FALSE, boards[currentMove]);
6905             fromX = fromY = -1; clearFlag = 0;
6906             return;
6907         }
6908         if (appData.animateDragging) {
6909             /* Undo animation damage if any */
6910             DrawPosition(FALSE, NULL);
6911         }
6912         if (second) {
6913             /* Second up/down in same square; just abort move */
6914             second = 0;
6915             fromX = fromY = -1;
6916             gatingPiece = EmptySquare;
6917             ClearHighlights();
6918             gotPremove = 0;
6919             ClearPremoveHighlights();
6920         } else {
6921             /* First upclick in same square; start click-click mode */
6922             SetHighlights(x, y, -1, -1);
6923         }
6924         return;
6925     }
6926
6927     clearFlag = 0;
6928
6929     /* we now have a different from- and (possibly off-board) to-square */
6930     /* Completed move */
6931     toX = x;
6932     toY = y;
6933     saveAnimate = appData.animate;
6934     if (clickType == Press) {
6935         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6936             // must be Edit Position mode with empty-square selected
6937             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6938             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6939             return;
6940         }
6941         /* Finish clickclick move */
6942         if (appData.animate || appData.highlightLastMove) {
6943             SetHighlights(fromX, fromY, toX, toY);
6944         } else {
6945             ClearHighlights();
6946         }
6947     } else {
6948         /* Finish drag move */
6949         if (appData.highlightLastMove) {
6950             SetHighlights(fromX, fromY, toX, toY);
6951         } else {
6952             ClearHighlights();
6953         }
6954         DragPieceEnd(xPix, yPix); dragging = 0;
6955         /* Don't animate move and drag both */
6956         appData.animate = FALSE;
6957     }
6958
6959     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6960     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6961         ChessSquare piece = boards[currentMove][fromY][fromX];
6962         if(gameMode == EditPosition && piece != EmptySquare &&
6963            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6964             int n;
6965
6966             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6967                 n = PieceToNumber(piece - (int)BlackPawn);
6968                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6969                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6970                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6971             } else
6972             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6973                 n = PieceToNumber(piece);
6974                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6975                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6976                 boards[currentMove][n][BOARD_WIDTH-2]++;
6977             }
6978             boards[currentMove][fromY][fromX] = EmptySquare;
6979         }
6980         ClearHighlights();
6981         fromX = fromY = -1;
6982         DrawPosition(TRUE, boards[currentMove]);
6983         return;
6984     }
6985
6986     // off-board moves should not be highlighted
6987     if(x < 0 || y < 0) ClearHighlights();
6988
6989     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6990
6991     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6992         SetHighlights(fromX, fromY, toX, toY);
6993         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6994             // [HGM] super: promotion to captured piece selected from holdings
6995             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6996             promotionChoice = TRUE;
6997             // kludge follows to temporarily execute move on display, without promoting yet
6998             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6999             boards[currentMove][toY][toX] = p;
7000             DrawPosition(FALSE, boards[currentMove]);
7001             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7002             boards[currentMove][toY][toX] = q;
7003             DisplayMessage("Click in holdings to choose piece", "");
7004             return;
7005         }
7006         PromotionPopUp();
7007     } else {
7008         int oldMove = currentMove;
7009         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7010         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7011         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7012         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7013            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7014             DrawPosition(TRUE, boards[currentMove]);
7015         fromX = fromY = -1;
7016     }
7017     appData.animate = saveAnimate;
7018     if (appData.animate || appData.animateDragging) {
7019         /* Undo animation damage if needed */
7020         DrawPosition(FALSE, NULL);
7021     }
7022 }
7023
7024 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7025 {   // front-end-free part taken out of PieceMenuPopup
7026     int whichMenu; int xSqr, ySqr;
7027
7028     if(seekGraphUp) { // [HGM] seekgraph
7029         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7030         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7031         return -2;
7032     }
7033
7034     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7035          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7036         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7037         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7038         if(action == Press)   {
7039             originalFlip = flipView;
7040             flipView = !flipView; // temporarily flip board to see game from partners perspective
7041             DrawPosition(TRUE, partnerBoard);
7042             DisplayMessage(partnerStatus, "");
7043             partnerUp = TRUE;
7044         } else if(action == Release) {
7045             flipView = originalFlip;
7046             DrawPosition(TRUE, boards[currentMove]);
7047             partnerUp = FALSE;
7048         }
7049         return -2;
7050     }
7051
7052     xSqr = EventToSquare(x, BOARD_WIDTH);
7053     ySqr = EventToSquare(y, BOARD_HEIGHT);
7054     if (action == Release) {
7055         if(pieceSweep != EmptySquare) {
7056             EditPositionMenuEvent(pieceSweep, toX, toY);
7057             pieceSweep = EmptySquare;
7058         } else UnLoadPV(); // [HGM] pv
7059     }
7060     if (action != Press) return -2; // return code to be ignored
7061     switch (gameMode) {
7062       case IcsExamining:
7063         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7064       case EditPosition:
7065         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7066         if (xSqr < 0 || ySqr < 0) return -1;
7067         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7068         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7069         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7070         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7071         NextPiece(0);
7072         return -2;\r
7073       case IcsObserving:
7074         if(!appData.icsEngineAnalyze) return -1;
7075       case IcsPlayingWhite:
7076       case IcsPlayingBlack:
7077         if(!appData.zippyPlay) goto noZip;
7078       case AnalyzeMode:
7079       case AnalyzeFile:
7080       case MachinePlaysWhite:
7081       case MachinePlaysBlack:
7082       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7083         if (!appData.dropMenu) {
7084           LoadPV(x, y);
7085           return 2; // flag front-end to grab mouse events
7086         }
7087         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7088            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7089       case EditGame:
7090       noZip:
7091         if (xSqr < 0 || ySqr < 0) return -1;
7092         if (!appData.dropMenu || appData.testLegality &&
7093             gameInfo.variant != VariantBughouse &&
7094             gameInfo.variant != VariantCrazyhouse) return -1;
7095         whichMenu = 1; // drop menu
7096         break;
7097       default:
7098         return -1;
7099     }
7100
7101     if (((*fromX = xSqr) < 0) ||
7102         ((*fromY = ySqr) < 0)) {
7103         *fromX = *fromY = -1;
7104         return -1;
7105     }
7106     if (flipView)
7107       *fromX = BOARD_WIDTH - 1 - *fromX;
7108     else
7109       *fromY = BOARD_HEIGHT - 1 - *fromY;
7110
7111     return whichMenu;
7112 }
7113
7114 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7115 {
7116 //    char * hint = lastHint;
7117     FrontEndProgramStats stats;
7118
7119     stats.which = cps == &first ? 0 : 1;
7120     stats.depth = cpstats->depth;
7121     stats.nodes = cpstats->nodes;
7122     stats.score = cpstats->score;
7123     stats.time = cpstats->time;
7124     stats.pv = cpstats->movelist;
7125     stats.hint = lastHint;
7126     stats.an_move_index = 0;
7127     stats.an_move_count = 0;
7128
7129     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7130         stats.hint = cpstats->move_name;
7131         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7132         stats.an_move_count = cpstats->nr_moves;
7133     }
7134
7135     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
7136
7137     SetProgramStats( &stats );
7138 }
7139
7140 void
7141 ClearEngineOutputPane(int which)
7142 {
7143     static FrontEndProgramStats dummyStats;
7144     dummyStats.which = which;
7145     dummyStats.pv = "#";
7146     SetProgramStats( &dummyStats );
7147 }
7148
7149 #define MAXPLAYERS 500
7150
7151 char *
7152 TourneyStandings(int display)
7153 {
7154     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7155     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7156     char result, *p, *names[MAXPLAYERS];
7157
7158     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7159         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7160     names[0] = p = strdup(appData.participants);
7161     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7162
7163     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7164
7165     while(result = appData.results[nr]) {
7166         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7167         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7168         wScore = bScore = 0;
7169         switch(result) {
7170           case '+': wScore = 2; break;
7171           case '-': bScore = 2; break;
7172           case '=': wScore = bScore = 1; break;
7173           case ' ':
7174           case '*': return strdup("busy"); // tourney not finished
7175         }
7176         score[w] += wScore;
7177         score[b] += bScore;
7178         games[w]++;
7179         games[b]++;
7180         nr++;
7181     }
7182     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7183     for(w=0; w<nPlayers; w++) {
7184         bScore = -1;
7185         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7186         ranking[w] = b; points[w] = bScore; score[b] = -2;
7187     }
7188     p = malloc(nPlayers*34+1);
7189     for(w=0; w<nPlayers && w<display; w++)
7190         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7191     free(names[0]);
7192     return p;
7193 }
7194
7195 void
7196 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7197 {       // count all piece types
7198         int p, f, r;
7199         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7200         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7201         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7202                 p = board[r][f];
7203                 pCnt[p]++;
7204                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7205                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7206                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7207                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7208                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7209                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7210         }
7211 }
7212
7213 int
7214 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7215 {
7216         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7217         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7218
7219         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7220         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7221         if(myPawns == 2 && nMine == 3) // KPP
7222             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7223         if(myPawns == 1 && nMine == 2) // KP
7224             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7225         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7226             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7227         if(myPawns) return FALSE;
7228         if(pCnt[WhiteRook+side])
7229             return pCnt[BlackRook-side] ||
7230                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7231                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7232                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7233         if(pCnt[WhiteCannon+side]) {
7234             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7235             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7236         }
7237         if(pCnt[WhiteKnight+side])
7238             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7239         return FALSE;
7240 }
7241
7242 int
7243 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7244 {
7245         VariantClass v = gameInfo.variant;
7246
7247         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7248         if(v == VariantShatranj) return TRUE; // always winnable through baring
7249         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7250         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7251
7252         if(v == VariantXiangqi) {
7253                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7254
7255                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7256                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7257                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7258                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7259                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7260                 if(stale) // we have at least one last-rank P plus perhaps C
7261                     return majors // KPKX
7262                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7263                 else // KCA*E*
7264                     return pCnt[WhiteFerz+side] // KCAK
7265                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7266                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7267                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7268
7269         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7270                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7271
7272                 if(nMine == 1) return FALSE; // bare King
7273                 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
7274                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7275                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7276                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7277                 if(pCnt[WhiteKnight+side])
7278                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7279                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7280                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7281                 if(nBishops)
7282                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7283                 if(pCnt[WhiteAlfil+side])
7284                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7285                 if(pCnt[WhiteWazir+side])
7286                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7287         }
7288
7289         return TRUE;
7290 }
7291
7292 int
7293 Adjudicate(ChessProgramState *cps)
7294 {       // [HGM] some adjudications useful with buggy engines
7295         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7296         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7297         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7298         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7299         int k, count = 0; static int bare = 1;
7300         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7301         Boolean canAdjudicate = !appData.icsActive;
7302
7303         // most tests only when we understand the game, i.e. legality-checking on
7304             if( appData.testLegality )
7305             {   /* [HGM] Some more adjudications for obstinate engines */
7306                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7307                 static int moveCount = 6;
7308                 ChessMove result;
7309                 char *reason = NULL;
7310
7311                 /* Count what is on board. */
7312                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7313
7314                 /* Some material-based adjudications that have to be made before stalemate test */
7315                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7316                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7317                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7318                      if(canAdjudicate && appData.checkMates) {
7319                          if(engineOpponent)
7320                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7321                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7322                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7323                          return 1;
7324                      }
7325                 }
7326
7327                 /* Bare King in Shatranj (loses) or Losers (wins) */
7328                 if( nrW == 1 || nrB == 1) {
7329                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7330                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7331                      if(canAdjudicate && appData.checkMates) {
7332                          if(engineOpponent)
7333                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7334                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7335                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7336                          return 1;
7337                      }
7338                   } else
7339                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7340                   {    /* bare King */
7341                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7342                         if(canAdjudicate && appData.checkMates) {
7343                             /* but only adjudicate if adjudication enabled */
7344                             if(engineOpponent)
7345                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7346                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7347                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7348                             return 1;
7349                         }
7350                   }
7351                 } else bare = 1;
7352
7353
7354             // don't wait for engine to announce game end if we can judge ourselves
7355             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7356               case MT_CHECK:
7357                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7358                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7359                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7360                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7361                             checkCnt++;
7362                         if(checkCnt >= 2) {
7363                             reason = "Xboard adjudication: 3rd check";
7364                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7365                             break;
7366                         }
7367                     }
7368                 }
7369               case MT_NONE:
7370               default:
7371                 break;
7372               case MT_STALEMATE:
7373               case MT_STAINMATE:
7374                 reason = "Xboard adjudication: Stalemate";
7375                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7376                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7377                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7378                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7379                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7380                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7381                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7382                                                                         EP_CHECKMATE : EP_WINS);
7383                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7384                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7385                 }
7386                 break;
7387               case MT_CHECKMATE:
7388                 reason = "Xboard adjudication: Checkmate";
7389                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7390                 break;
7391             }
7392
7393                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7394                     case EP_STALEMATE:
7395                         result = GameIsDrawn; break;
7396                     case EP_CHECKMATE:
7397                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7398                     case EP_WINS:
7399                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7400                     default:
7401                         result = EndOfFile;
7402                 }
7403                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7404                     if(engineOpponent)
7405                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7406                     GameEnds( result, reason, GE_XBOARD );
7407                     return 1;
7408                 }
7409
7410                 /* Next absolutely insufficient mating material. */
7411                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7412                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7413                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7414
7415                      /* always flag draws, for judging claims */
7416                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7417
7418                      if(canAdjudicate && appData.materialDraws) {
7419                          /* but only adjudicate them if adjudication enabled */
7420                          if(engineOpponent) {
7421                            SendToProgram("force\n", engineOpponent); // suppress reply
7422                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7423                          }
7424                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7425                          return 1;
7426                      }
7427                 }
7428
7429                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7430                 if(gameInfo.variant == VariantXiangqi ?
7431                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7432                  : nrW + nrB == 4 &&
7433                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7434                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7435                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7436                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7437                    ) ) {
7438                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7439                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7440                           if(engineOpponent) {
7441                             SendToProgram("force\n", engineOpponent); // suppress reply
7442                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7443                           }
7444                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7445                           return 1;
7446                      }
7447                 } else moveCount = 6;
7448             }
7449         if (appData.debugMode) { int i;
7450             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7451                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7452                     appData.drawRepeats);
7453             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7454               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7455
7456         }
7457
7458         // Repetition draws and 50-move rule can be applied independently of legality testing
7459
7460                 /* Check for rep-draws */
7461                 count = 0;
7462                 for(k = forwardMostMove-2;
7463                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7464                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7465                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7466                     k-=2)
7467                 {   int rights=0;
7468                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7469                         /* compare castling rights */
7470                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7471                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7472                                 rights++; /* King lost rights, while rook still had them */
7473                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7474                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7475                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7476                                    rights++; /* but at least one rook lost them */
7477                         }
7478                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7479                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7480                                 rights++;
7481                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7482                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7483                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7484                                    rights++;
7485                         }
7486                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7487                             && appData.drawRepeats > 1) {
7488                              /* adjudicate after user-specified nr of repeats */
7489                              int result = GameIsDrawn;
7490                              char *details = "XBoard adjudication: repetition draw";
7491                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7492                                 // [HGM] xiangqi: check for forbidden perpetuals
7493                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7494                                 for(m=forwardMostMove; m>k; m-=2) {
7495                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7496                                         ourPerpetual = 0; // the current mover did not always check
7497                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7498                                         hisPerpetual = 0; // the opponent did not always check
7499                                 }
7500                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7501                                                                         ourPerpetual, hisPerpetual);
7502                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7503                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7504                                     details = "Xboard adjudication: perpetual checking";
7505                                 } else
7506                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7507                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7508                                 } else
7509                                 // Now check for perpetual chases
7510                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7511                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7512                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7513                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7514                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7515                                         details = "Xboard adjudication: perpetual chasing";
7516                                     } else
7517                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7518                                         break; // Abort repetition-checking loop.
7519                                 }
7520                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7521                              }
7522                              if(engineOpponent) {
7523                                SendToProgram("force\n", engineOpponent); // suppress reply
7524                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7525                              }
7526                              GameEnds( result, details, GE_XBOARD );
7527                              return 1;
7528                         }
7529                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7530                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7531                     }
7532                 }
7533
7534                 /* Now we test for 50-move draws. Determine ply count */
7535                 count = forwardMostMove;
7536                 /* look for last irreversble move */
7537                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7538                     count--;
7539                 /* if we hit starting position, add initial plies */
7540                 if( count == backwardMostMove )
7541                     count -= initialRulePlies;
7542                 count = forwardMostMove - count;
7543                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7544                         // adjust reversible move counter for checks in Xiangqi
7545                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7546                         if(i < backwardMostMove) i = backwardMostMove;
7547                         while(i <= forwardMostMove) {
7548                                 lastCheck = inCheck; // check evasion does not count
7549                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7550                                 if(inCheck || lastCheck) count--; // check does not count
7551                                 i++;
7552                         }
7553                 }
7554                 if( count >= 100)
7555                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7556                          /* this is used to judge if draw claims are legal */
7557                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7558                          if(engineOpponent) {
7559                            SendToProgram("force\n", engineOpponent); // suppress reply
7560                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7561                          }
7562                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7563                          return 1;
7564                 }
7565
7566                 /* if draw offer is pending, treat it as a draw claim
7567                  * when draw condition present, to allow engines a way to
7568                  * claim draws before making their move to avoid a race
7569                  * condition occurring after their move
7570                  */
7571                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7572                          char *p = NULL;
7573                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7574                              p = "Draw claim: 50-move rule";
7575                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7576                              p = "Draw claim: 3-fold repetition";
7577                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7578                              p = "Draw claim: insufficient mating material";
7579                          if( p != NULL && canAdjudicate) {
7580                              if(engineOpponent) {
7581                                SendToProgram("force\n", engineOpponent); // suppress reply
7582                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7583                              }
7584                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7585                              return 1;
7586                          }
7587                 }
7588
7589                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7590                     if(engineOpponent) {
7591                       SendToProgram("force\n", engineOpponent); // suppress reply
7592                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7593                     }
7594                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7595                     return 1;
7596                 }
7597         return 0;
7598 }
7599
7600 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7601 {   // [HGM] book: this routine intercepts moves to simulate book replies
7602     char *bookHit = NULL;
7603
7604     //first determine if the incoming move brings opponent into his book
7605     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7606         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7607     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7608     if(bookHit != NULL && !cps->bookSuspend) {
7609         // make sure opponent is not going to reply after receiving move to book position
7610         SendToProgram("force\n", cps);
7611         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7612     }
7613     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7614     // now arrange restart after book miss
7615     if(bookHit) {
7616         // after a book hit we never send 'go', and the code after the call to this routine
7617         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7618         char buf[MSG_SIZ], *move = bookHit;
7619         if(cps->useSAN) {
7620             int fromX, fromY, toX, toY;
7621             char promoChar;
7622             ChessMove moveType;
7623             move = buf + 30;
7624             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7625                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7626                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7627                                     PosFlags(forwardMostMove),
7628                                     fromY, fromX, toY, toX, promoChar, move);
7629             } else {
7630                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7631                 bookHit = NULL;
7632             }
7633         }
7634         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7635         SendToProgram(buf, cps);
7636         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7637     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7638         SendToProgram("go\n", cps);
7639         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7640     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7641         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7642             SendToProgram("go\n", cps);
7643         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7644     }
7645     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7646 }
7647
7648 char *savedMessage;
7649 ChessProgramState *savedState;
7650 void DeferredBookMove(void)
7651 {
7652         if(savedState->lastPing != savedState->lastPong)
7653                     ScheduleDelayedEvent(DeferredBookMove, 10);
7654         else
7655         HandleMachineMove(savedMessage, savedState);
7656 }
7657
7658 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7659
7660 void
7661 HandleMachineMove(message, cps)
7662      char *message;
7663      ChessProgramState *cps;
7664 {
7665     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7666     char realname[MSG_SIZ];
7667     int fromX, fromY, toX, toY;
7668     ChessMove moveType;
7669     char promoChar;
7670     char *p;
7671     int machineWhite;
7672     char *bookHit;
7673
7674     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7675         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7676         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7677             DisplayError(_("Invalid pairing from pairing engine"), 0);
7678             return;
7679         }
7680         pairingReceived = 1;
7681         NextMatchGame();
7682         return; // Skim the pairing messages here.
7683     }
7684
7685     cps->userError = 0;
7686
7687 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7688     /*
7689      * Kludge to ignore BEL characters
7690      */
7691     while (*message == '\007') message++;
7692
7693     /*
7694      * [HGM] engine debug message: ignore lines starting with '#' character
7695      */
7696     if(cps->debug && *message == '#') return;
7697
7698     /*
7699      * Look for book output
7700      */
7701     if (cps == &first && bookRequested) {
7702         if (message[0] == '\t' || message[0] == ' ') {
7703             /* Part of the book output is here; append it */
7704             strcat(bookOutput, message);
7705             strcat(bookOutput, "  \n");
7706             return;
7707         } else if (bookOutput[0] != NULLCHAR) {
7708             /* All of book output has arrived; display it */
7709             char *p = bookOutput;
7710             while (*p != NULLCHAR) {
7711                 if (*p == '\t') *p = ' ';
7712                 p++;
7713             }
7714             DisplayInformation(bookOutput);
7715             bookRequested = FALSE;
7716             /* Fall through to parse the current output */
7717         }
7718     }
7719
7720     /*
7721      * Look for machine move.
7722      */
7723     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7724         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7725     {
7726         /* This method is only useful on engines that support ping */
7727         if (cps->lastPing != cps->lastPong) {
7728           if (gameMode == BeginningOfGame) {
7729             /* Extra move from before last new; ignore */
7730             if (appData.debugMode) {
7731                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7732             }
7733           } else {
7734             if (appData.debugMode) {
7735                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7736                         cps->which, gameMode);
7737             }
7738
7739             SendToProgram("undo\n", cps);
7740           }
7741           return;
7742         }
7743
7744         switch (gameMode) {
7745           case BeginningOfGame:
7746             /* Extra move from before last reset; ignore */
7747             if (appData.debugMode) {
7748                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7749             }
7750             return;
7751
7752           case EndOfGame:
7753           case IcsIdle:
7754           default:
7755             /* Extra move after we tried to stop.  The mode test is
7756                not a reliable way of detecting this problem, but it's
7757                the best we can do on engines that don't support ping.
7758             */
7759             if (appData.debugMode) {
7760                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7761                         cps->which, gameMode);
7762             }
7763             SendToProgram("undo\n", cps);
7764             return;
7765
7766           case MachinePlaysWhite:
7767           case IcsPlayingWhite:
7768             machineWhite = TRUE;
7769             break;
7770
7771           case MachinePlaysBlack:
7772           case IcsPlayingBlack:
7773             machineWhite = FALSE;
7774             break;
7775
7776           case TwoMachinesPlay:
7777             machineWhite = (cps->twoMachinesColor[0] == 'w');
7778             break;
7779         }
7780         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7781             if (appData.debugMode) {
7782                 fprintf(debugFP,
7783                         "Ignoring move out of turn by %s, gameMode %d"
7784                         ", forwardMost %d\n",
7785                         cps->which, gameMode, forwardMostMove);
7786             }
7787             return;
7788         }
7789
7790     if (appData.debugMode) { int f = forwardMostMove;
7791         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7792                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7793                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7794     }
7795         if(cps->alphaRank) AlphaRank(machineMove, 4);
7796         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7797                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7798             /* Machine move could not be parsed; ignore it. */
7799           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7800                     machineMove, _(cps->which));
7801             DisplayError(buf1, 0);
7802             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7803                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7804             if (gameMode == TwoMachinesPlay) {
7805               GameEnds(machineWhite ? BlackWins : WhiteWins,
7806                        buf1, GE_XBOARD);
7807             }
7808             return;
7809         }
7810
7811         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7812         /* So we have to redo legality test with true e.p. status here,  */
7813         /* to make sure an illegal e.p. capture does not slip through,   */
7814         /* to cause a forfeit on a justified illegal-move complaint      */
7815         /* of the opponent.                                              */
7816         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7817            ChessMove moveType;
7818            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7819                              fromY, fromX, toY, toX, promoChar);
7820             if (appData.debugMode) {
7821                 int i;
7822                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7823                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7824                 fprintf(debugFP, "castling rights\n");
7825             }
7826             if(moveType == IllegalMove) {
7827               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7828                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7829                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7830                            buf1, GE_XBOARD);
7831                 return;
7832            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7833            /* [HGM] Kludge to handle engines that send FRC-style castling
7834               when they shouldn't (like TSCP-Gothic) */
7835            switch(moveType) {
7836              case WhiteASideCastleFR:
7837              case BlackASideCastleFR:
7838                toX+=2;
7839                currentMoveString[2]++;
7840                break;
7841              case WhiteHSideCastleFR:
7842              case BlackHSideCastleFR:
7843                toX--;
7844                currentMoveString[2]--;
7845                break;
7846              default: ; // nothing to do, but suppresses warning of pedantic compilers
7847            }
7848         }
7849         hintRequested = FALSE;
7850         lastHint[0] = NULLCHAR;
7851         bookRequested = FALSE;
7852         /* Program may be pondering now */
7853         cps->maybeThinking = TRUE;
7854         if (cps->sendTime == 2) cps->sendTime = 1;
7855         if (cps->offeredDraw) cps->offeredDraw--;
7856
7857         /* [AS] Save move info*/
7858         pvInfoList[ forwardMostMove ].score = programStats.score;
7859         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7860         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7861
7862         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7863
7864         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7865         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7866             int count = 0;
7867
7868             while( count < adjudicateLossPlies ) {
7869                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7870
7871                 if( count & 1 ) {
7872                     score = -score; /* Flip score for winning side */
7873                 }
7874
7875                 if( score > adjudicateLossThreshold ) {
7876                     break;
7877                 }
7878
7879                 count++;
7880             }
7881
7882             if( count >= adjudicateLossPlies ) {
7883                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7884
7885                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7886                     "Xboard adjudication",
7887                     GE_XBOARD );
7888
7889                 return;
7890             }
7891         }
7892
7893         if(Adjudicate(cps)) {
7894             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7895             return; // [HGM] adjudicate: for all automatic game ends
7896         }
7897
7898 #if ZIPPY
7899         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7900             first.initDone) {
7901           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7902                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7903                 SendToICS("draw ");
7904                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7905           }
7906           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7907           ics_user_moved = 1;
7908           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7909                 char buf[3*MSG_SIZ];
7910
7911                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7912                         programStats.score / 100.,
7913                         programStats.depth,
7914                         programStats.time / 100.,
7915                         (unsigned int)programStats.nodes,
7916                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7917                         programStats.movelist);
7918                 SendToICS(buf);
7919 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7920           }
7921         }
7922 #endif
7923
7924         /* [AS] Clear stats for next move */
7925         ClearProgramStats();
7926         thinkOutput[0] = NULLCHAR;
7927         hiddenThinkOutputState = 0;
7928
7929         bookHit = NULL;
7930         if (gameMode == TwoMachinesPlay) {
7931             /* [HGM] relaying draw offers moved to after reception of move */
7932             /* and interpreting offer as claim if it brings draw condition */
7933             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7934                 SendToProgram("draw\n", cps->other);
7935             }
7936             if (cps->other->sendTime) {
7937                 SendTimeRemaining(cps->other,
7938                                   cps->other->twoMachinesColor[0] == 'w');
7939             }
7940             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7941             if (firstMove && !bookHit) {
7942                 firstMove = FALSE;
7943                 if (cps->other->useColors) {
7944                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7945                 }
7946                 SendToProgram("go\n", cps->other);
7947             }
7948             cps->other->maybeThinking = TRUE;
7949         }
7950
7951         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7952
7953         if (!pausing && appData.ringBellAfterMoves) {
7954             RingBell();
7955         }
7956
7957         /*
7958          * Reenable menu items that were disabled while
7959          * machine was thinking
7960          */
7961         if (gameMode != TwoMachinesPlay)
7962             SetUserThinkingEnables();
7963
7964         // [HGM] book: after book hit opponent has received move and is now in force mode
7965         // force the book reply into it, and then fake that it outputted this move by jumping
7966         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7967         if(bookHit) {
7968                 static char bookMove[MSG_SIZ]; // a bit generous?
7969
7970                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7971                 strcat(bookMove, bookHit);
7972                 message = bookMove;
7973                 cps = cps->other;
7974                 programStats.nodes = programStats.depth = programStats.time =
7975                 programStats.score = programStats.got_only_move = 0;
7976                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7977
7978                 if(cps->lastPing != cps->lastPong) {
7979                     savedMessage = message; // args for deferred call
7980                     savedState = cps;
7981                     ScheduleDelayedEvent(DeferredBookMove, 10);
7982                     return;
7983                 }
7984                 goto FakeBookMove;
7985         }
7986
7987         return;
7988     }
7989
7990     /* Set special modes for chess engines.  Later something general
7991      *  could be added here; for now there is just one kludge feature,
7992      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7993      *  when "xboard" is given as an interactive command.
7994      */
7995     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7996         cps->useSigint = FALSE;
7997         cps->useSigterm = FALSE;
7998     }
7999     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8000       ParseFeatures(message+8, cps);
8001       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8002     }
8003
8004     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8005       int dummy, s=6; char buf[MSG_SIZ];
8006       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8007       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8008       ParseFEN(boards[0], &dummy, message+s);
8009       DrawPosition(TRUE, boards[0]);
8010       startedFromSetupPosition = TRUE;
8011       return;
8012     }
8013     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8014      * want this, I was asked to put it in, and obliged.
8015      */
8016     if (!strncmp(message, "setboard ", 9)) {
8017         Board initial_position;
8018
8019         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8020
8021         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8022             DisplayError(_("Bad FEN received from engine"), 0);
8023             return ;
8024         } else {
8025            Reset(TRUE, FALSE);
8026            CopyBoard(boards[0], initial_position);
8027            initialRulePlies = FENrulePlies;
8028            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8029            else gameMode = MachinePlaysBlack;
8030            DrawPosition(FALSE, boards[currentMove]);
8031         }
8032         return;
8033     }
8034
8035     /*
8036      * Look for communication commands
8037      */
8038     if (!strncmp(message, "telluser ", 9)) {
8039         if(message[9] == '\\' && message[10] == '\\')
8040             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8041         PlayTellSound();
8042         DisplayNote(message + 9);
8043         return;
8044     }
8045     if (!strncmp(message, "tellusererror ", 14)) {
8046         cps->userError = 1;
8047         if(message[14] == '\\' && message[15] == '\\')
8048             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8049         PlayTellSound();
8050         DisplayError(message + 14, 0);
8051         return;
8052     }
8053     if (!strncmp(message, "tellopponent ", 13)) {
8054       if (appData.icsActive) {
8055         if (loggedOn) {
8056           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8057           SendToICS(buf1);
8058         }
8059       } else {
8060         DisplayNote(message + 13);
8061       }
8062       return;
8063     }
8064     if (!strncmp(message, "tellothers ", 11)) {
8065       if (appData.icsActive) {
8066         if (loggedOn) {
8067           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8068           SendToICS(buf1);
8069         }
8070       }
8071       return;
8072     }
8073     if (!strncmp(message, "tellall ", 8)) {
8074       if (appData.icsActive) {
8075         if (loggedOn) {
8076           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8077           SendToICS(buf1);
8078         }
8079       } else {
8080         DisplayNote(message + 8);
8081       }
8082       return;
8083     }
8084     if (strncmp(message, "warning", 7) == 0) {
8085         /* Undocumented feature, use tellusererror in new code */
8086         DisplayError(message, 0);
8087         return;
8088     }
8089     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8090         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8091         strcat(realname, " query");
8092         AskQuestion(realname, buf2, buf1, cps->pr);
8093         return;
8094     }
8095     /* Commands from the engine directly to ICS.  We don't allow these to be
8096      *  sent until we are logged on. Crafty kibitzes have been known to
8097      *  interfere with the login process.
8098      */
8099     if (loggedOn) {
8100         if (!strncmp(message, "tellics ", 8)) {
8101             SendToICS(message + 8);
8102             SendToICS("\n");
8103             return;
8104         }
8105         if (!strncmp(message, "tellicsnoalias ", 15)) {
8106             SendToICS(ics_prefix);
8107             SendToICS(message + 15);
8108             SendToICS("\n");
8109             return;
8110         }
8111         /* The following are for backward compatibility only */
8112         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8113             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8114             SendToICS(ics_prefix);
8115             SendToICS(message);
8116             SendToICS("\n");
8117             return;
8118         }
8119     }
8120     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8121         return;
8122     }
8123     /*
8124      * If the move is illegal, cancel it and redraw the board.
8125      * Also deal with other error cases.  Matching is rather loose
8126      * here to accommodate engines written before the spec.
8127      */
8128     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8129         strncmp(message, "Error", 5) == 0) {
8130         if (StrStr(message, "name") ||
8131             StrStr(message, "rating") || StrStr(message, "?") ||
8132             StrStr(message, "result") || StrStr(message, "board") ||
8133             StrStr(message, "bk") || StrStr(message, "computer") ||
8134             StrStr(message, "variant") || StrStr(message, "hint") ||
8135             StrStr(message, "random") || StrStr(message, "depth") ||
8136             StrStr(message, "accepted")) {
8137             return;
8138         }
8139         if (StrStr(message, "protover")) {
8140           /* Program is responding to input, so it's apparently done
8141              initializing, and this error message indicates it is
8142              protocol version 1.  So we don't need to wait any longer
8143              for it to initialize and send feature commands. */
8144           FeatureDone(cps, 1);
8145           cps->protocolVersion = 1;
8146           return;
8147         }
8148         cps->maybeThinking = FALSE;
8149
8150         if (StrStr(message, "draw")) {
8151             /* Program doesn't have "draw" command */
8152             cps->sendDrawOffers = 0;
8153             return;
8154         }
8155         if (cps->sendTime != 1 &&
8156             (StrStr(message, "time") || StrStr(message, "otim"))) {
8157           /* Program apparently doesn't have "time" or "otim" command */
8158           cps->sendTime = 0;
8159           return;
8160         }
8161         if (StrStr(message, "analyze")) {
8162             cps->analysisSupport = FALSE;
8163             cps->analyzing = FALSE;
8164             Reset(FALSE, TRUE);
8165             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8166             DisplayError(buf2, 0);
8167             return;
8168         }
8169         if (StrStr(message, "(no matching move)st")) {
8170           /* Special kludge for GNU Chess 4 only */
8171           cps->stKludge = TRUE;
8172           SendTimeControl(cps, movesPerSession, timeControl,
8173                           timeIncrement, appData.searchDepth,
8174                           searchTime);
8175           return;
8176         }
8177         if (StrStr(message, "(no matching move)sd")) {
8178           /* Special kludge for GNU Chess 4 only */
8179           cps->sdKludge = TRUE;
8180           SendTimeControl(cps, movesPerSession, timeControl,
8181                           timeIncrement, appData.searchDepth,
8182                           searchTime);
8183           return;
8184         }
8185         if (!StrStr(message, "llegal")) {
8186             return;
8187         }
8188         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8189             gameMode == IcsIdle) return;
8190         if (forwardMostMove <= backwardMostMove) return;
8191         if (pausing) PauseEvent();
8192       if(appData.forceIllegal) {
8193             // [HGM] illegal: machine refused move; force position after move into it
8194           SendToProgram("force\n", cps);
8195           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8196                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8197                 // when black is to move, while there might be nothing on a2 or black
8198                 // might already have the move. So send the board as if white has the move.
8199                 // But first we must change the stm of the engine, as it refused the last move
8200                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8201                 if(WhiteOnMove(forwardMostMove)) {
8202                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8203                     SendBoard(cps, forwardMostMove); // kludgeless board
8204                 } else {
8205                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8206                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8207                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8208                 }
8209           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8210             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8211                  gameMode == TwoMachinesPlay)
8212               SendToProgram("go\n", cps);
8213             return;
8214       } else
8215         if (gameMode == PlayFromGameFile) {
8216             /* Stop reading this game file */
8217             gameMode = EditGame;
8218             ModeHighlight();
8219         }
8220         /* [HGM] illegal-move claim should forfeit game when Xboard */
8221         /* only passes fully legal moves                            */
8222         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8223             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8224                                 "False illegal-move claim", GE_XBOARD );
8225             return; // do not take back move we tested as valid
8226         }
8227         currentMove = forwardMostMove-1;
8228         DisplayMove(currentMove-1); /* before DisplayMoveError */
8229         SwitchClocks(forwardMostMove-1); // [HGM] race
8230         DisplayBothClocks();
8231         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8232                 parseList[currentMove], _(cps->which));
8233         DisplayMoveError(buf1);
8234         DrawPosition(FALSE, boards[currentMove]);
8235         return;
8236     }
8237     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8238         /* Program has a broken "time" command that
8239            outputs a string not ending in newline.
8240            Don't use it. */
8241         cps->sendTime = 0;
8242     }
8243
8244     /*
8245      * If chess program startup fails, exit with an error message.
8246      * Attempts to recover here are futile.
8247      */
8248     if ((StrStr(message, "unknown host") != NULL)
8249         || (StrStr(message, "No remote directory") != NULL)
8250         || (StrStr(message, "not found") != NULL)
8251         || (StrStr(message, "No such file") != NULL)
8252         || (StrStr(message, "can't alloc") != NULL)
8253         || (StrStr(message, "Permission denied") != NULL)) {
8254
8255         cps->maybeThinking = FALSE;
8256         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8257                 _(cps->which), cps->program, cps->host, message);
8258         RemoveInputSource(cps->isr);
8259         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8260             if(cps == &first) appData.noChessProgram = TRUE;
8261             DisplayError(buf1, 0);
8262         }
8263         return;
8264     }
8265
8266     /*
8267      * Look for hint output
8268      */
8269     if (sscanf(message, "Hint: %s", buf1) == 1) {
8270         if (cps == &first && hintRequested) {
8271             hintRequested = FALSE;
8272             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8273                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8274                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8275                                     PosFlags(forwardMostMove),
8276                                     fromY, fromX, toY, toX, promoChar, buf1);
8277                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8278                 DisplayInformation(buf2);
8279             } else {
8280                 /* Hint move could not be parsed!? */
8281               snprintf(buf2, sizeof(buf2),
8282                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8283                         buf1, _(cps->which));
8284                 DisplayError(buf2, 0);
8285             }
8286         } else {
8287           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8288         }
8289         return;
8290     }
8291
8292     /*
8293      * Ignore other messages if game is not in progress
8294      */
8295     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8296         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8297
8298     /*
8299      * look for win, lose, draw, or draw offer
8300      */
8301     if (strncmp(message, "1-0", 3) == 0) {
8302         char *p, *q, *r = "";
8303         p = strchr(message, '{');
8304         if (p) {
8305             q = strchr(p, '}');
8306             if (q) {
8307                 *q = NULLCHAR;
8308                 r = p + 1;
8309             }
8310         }
8311         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8312         return;
8313     } else if (strncmp(message, "0-1", 3) == 0) {
8314         char *p, *q, *r = "";
8315         p = strchr(message, '{');
8316         if (p) {
8317             q = strchr(p, '}');
8318             if (q) {
8319                 *q = NULLCHAR;
8320                 r = p + 1;
8321             }
8322         }
8323         /* Kludge for Arasan 4.1 bug */
8324         if (strcmp(r, "Black resigns") == 0) {
8325             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8326             return;
8327         }
8328         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8329         return;
8330     } else if (strncmp(message, "1/2", 3) == 0) {
8331         char *p, *q, *r = "";
8332         p = strchr(message, '{');
8333         if (p) {
8334             q = strchr(p, '}');
8335             if (q) {
8336                 *q = NULLCHAR;
8337                 r = p + 1;
8338             }
8339         }
8340
8341         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8342         return;
8343
8344     } else if (strncmp(message, "White resign", 12) == 0) {
8345         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8346         return;
8347     } else if (strncmp(message, "Black resign", 12) == 0) {
8348         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8349         return;
8350     } else if (strncmp(message, "White matches", 13) == 0 ||
8351                strncmp(message, "Black matches", 13) == 0   ) {
8352         /* [HGM] ignore GNUShogi noises */
8353         return;
8354     } else if (strncmp(message, "White", 5) == 0 &&
8355                message[5] != '(' &&
8356                StrStr(message, "Black") == NULL) {
8357         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8358         return;
8359     } else if (strncmp(message, "Black", 5) == 0 &&
8360                message[5] != '(') {
8361         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8362         return;
8363     } else if (strcmp(message, "resign") == 0 ||
8364                strcmp(message, "computer resigns") == 0) {
8365         switch (gameMode) {
8366           case MachinePlaysBlack:
8367           case IcsPlayingBlack:
8368             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8369             break;
8370           case MachinePlaysWhite:
8371           case IcsPlayingWhite:
8372             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8373             break;
8374           case TwoMachinesPlay:
8375             if (cps->twoMachinesColor[0] == 'w')
8376               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8377             else
8378               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8379             break;
8380           default:
8381             /* can't happen */
8382             break;
8383         }
8384         return;
8385     } else if (strncmp(message, "opponent mates", 14) == 0) {
8386         switch (gameMode) {
8387           case MachinePlaysBlack:
8388           case IcsPlayingBlack:
8389             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8390             break;
8391           case MachinePlaysWhite:
8392           case IcsPlayingWhite:
8393             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8394             break;
8395           case TwoMachinesPlay:
8396             if (cps->twoMachinesColor[0] == 'w')
8397               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8398             else
8399               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8400             break;
8401           default:
8402             /* can't happen */
8403             break;
8404         }
8405         return;
8406     } else if (strncmp(message, "computer mates", 14) == 0) {
8407         switch (gameMode) {
8408           case MachinePlaysBlack:
8409           case IcsPlayingBlack:
8410             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8411             break;
8412           case MachinePlaysWhite:
8413           case IcsPlayingWhite:
8414             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8415             break;
8416           case TwoMachinesPlay:
8417             if (cps->twoMachinesColor[0] == 'w')
8418               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8419             else
8420               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8421             break;
8422           default:
8423             /* can't happen */
8424             break;
8425         }
8426         return;
8427     } else if (strncmp(message, "checkmate", 9) == 0) {
8428         if (WhiteOnMove(forwardMostMove)) {
8429             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8430         } else {
8431             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8432         }
8433         return;
8434     } else if (strstr(message, "Draw") != NULL ||
8435                strstr(message, "game is a draw") != NULL) {
8436         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8437         return;
8438     } else if (strstr(message, "offer") != NULL &&
8439                strstr(message, "draw") != NULL) {
8440 #if ZIPPY
8441         if (appData.zippyPlay && first.initDone) {
8442             /* Relay offer to ICS */
8443             SendToICS(ics_prefix);
8444             SendToICS("draw\n");
8445         }
8446 #endif
8447         cps->offeredDraw = 2; /* valid until this engine moves twice */
8448         if (gameMode == TwoMachinesPlay) {
8449             if (cps->other->offeredDraw) {
8450                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8451             /* [HGM] in two-machine mode we delay relaying draw offer      */
8452             /* until after we also have move, to see if it is really claim */
8453             }
8454         } else if (gameMode == MachinePlaysWhite ||
8455                    gameMode == MachinePlaysBlack) {
8456           if (userOfferedDraw) {
8457             DisplayInformation(_("Machine accepts your draw offer"));
8458             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8459           } else {
8460             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8461           }
8462         }
8463     }
8464
8465
8466     /*
8467      * Look for thinking output
8468      */
8469     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8470           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8471                                 ) {
8472         int plylev, mvleft, mvtot, curscore, time;
8473         char mvname[MOVE_LEN];
8474         u64 nodes; // [DM]
8475         char plyext;
8476         int ignore = FALSE;
8477         int prefixHint = FALSE;
8478         mvname[0] = NULLCHAR;
8479
8480         switch (gameMode) {
8481           case MachinePlaysBlack:
8482           case IcsPlayingBlack:
8483             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8484             break;
8485           case MachinePlaysWhite:
8486           case IcsPlayingWhite:
8487             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8488             break;
8489           case AnalyzeMode:
8490           case AnalyzeFile:
8491             break;
8492           case IcsObserving: /* [DM] icsEngineAnalyze */
8493             if (!appData.icsEngineAnalyze) ignore = TRUE;
8494             break;
8495           case TwoMachinesPlay:
8496             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8497                 ignore = TRUE;
8498             }
8499             break;
8500           default:
8501             ignore = TRUE;
8502             break;
8503         }
8504
8505         if (!ignore) {
8506             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8507             buf1[0] = NULLCHAR;
8508             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8509                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8510
8511                 if (plyext != ' ' && plyext != '\t') {
8512                     time *= 100;
8513                 }
8514
8515                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8516                 if( cps->scoreIsAbsolute &&
8517                     ( gameMode == MachinePlaysBlack ||
8518                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8519                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8520                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8521                      !WhiteOnMove(currentMove)
8522                     ) )
8523                 {
8524                     curscore = -curscore;
8525                 }
8526
8527
8528                 tempStats.depth = plylev;
8529                 tempStats.nodes = nodes;
8530                 tempStats.time = time;
8531                 tempStats.score = curscore;
8532                 tempStats.got_only_move = 0;
8533
8534                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8535                         int ticklen;
8536
8537                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8538                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8539                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8540                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8541                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8542                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8543                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8544                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8545                 }
8546
8547                 /* Buffer overflow protection */
8548                 if (buf1[0] != NULLCHAR) {
8549                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8550                         && appData.debugMode) {
8551                         fprintf(debugFP,
8552                                 "PV is too long; using the first %u bytes.\n",
8553                                 (unsigned) sizeof(tempStats.movelist) - 1);
8554                     }
8555
8556                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8557                 } else {
8558                     sprintf(tempStats.movelist, " no PV\n");
8559                 }
8560
8561                 if (tempStats.seen_stat) {
8562                     tempStats.ok_to_send = 1;
8563                 }
8564
8565                 if (strchr(tempStats.movelist, '(') != NULL) {
8566                     tempStats.line_is_book = 1;
8567                     tempStats.nr_moves = 0;
8568                     tempStats.moves_left = 0;
8569                 } else {
8570                     tempStats.line_is_book = 0;
8571                 }
8572
8573                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8574                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8575
8576                 SendProgramStatsToFrontend( cps, &tempStats );
8577
8578                 /*
8579                     [AS] Protect the thinkOutput buffer from overflow... this
8580                     is only useful if buf1 hasn't overflowed first!
8581                 */
8582                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8583                          plylev,
8584                          (gameMode == TwoMachinesPlay ?
8585                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8586                          ((double) curscore) / 100.0,
8587                          prefixHint ? lastHint : "",
8588                          prefixHint ? " " : "" );
8589
8590                 if( buf1[0] != NULLCHAR ) {
8591                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8592
8593                     if( strlen(buf1) > max_len ) {
8594                         if( appData.debugMode) {
8595                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8596                         }
8597                         buf1[max_len+1] = '\0';
8598                     }
8599
8600                     strcat( thinkOutput, buf1 );
8601                 }
8602
8603                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8604                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8605                     DisplayMove(currentMove - 1);
8606                 }
8607                 return;
8608
8609             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8610                 /* crafty (9.25+) says "(only move) <move>"
8611                  * if there is only 1 legal move
8612                  */
8613                 sscanf(p, "(only move) %s", buf1);
8614                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8615                 sprintf(programStats.movelist, "%s (only move)", buf1);
8616                 programStats.depth = 1;
8617                 programStats.nr_moves = 1;
8618                 programStats.moves_left = 1;
8619                 programStats.nodes = 1;
8620                 programStats.time = 1;
8621                 programStats.got_only_move = 1;
8622
8623                 /* Not really, but we also use this member to
8624                    mean "line isn't going to change" (Crafty
8625                    isn't searching, so stats won't change) */
8626                 programStats.line_is_book = 1;
8627
8628                 SendProgramStatsToFrontend( cps, &programStats );
8629
8630                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8631                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8632                     DisplayMove(currentMove - 1);
8633                 }
8634                 return;
8635             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8636                               &time, &nodes, &plylev, &mvleft,
8637                               &mvtot, mvname) >= 5) {
8638                 /* The stat01: line is from Crafty (9.29+) in response
8639                    to the "." command */
8640                 programStats.seen_stat = 1;
8641                 cps->maybeThinking = TRUE;
8642
8643                 if (programStats.got_only_move || !appData.periodicUpdates)
8644                   return;
8645
8646                 programStats.depth = plylev;
8647                 programStats.time = time;
8648                 programStats.nodes = nodes;
8649                 programStats.moves_left = mvleft;
8650                 programStats.nr_moves = mvtot;
8651                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8652                 programStats.ok_to_send = 1;
8653                 programStats.movelist[0] = '\0';
8654
8655                 SendProgramStatsToFrontend( cps, &programStats );
8656
8657                 return;
8658
8659             } else if (strncmp(message,"++",2) == 0) {
8660                 /* Crafty 9.29+ outputs this */
8661                 programStats.got_fail = 2;
8662                 return;
8663
8664             } else if (strncmp(message,"--",2) == 0) {
8665                 /* Crafty 9.29+ outputs this */
8666                 programStats.got_fail = 1;
8667                 return;
8668
8669             } else if (thinkOutput[0] != NULLCHAR &&
8670                        strncmp(message, "    ", 4) == 0) {
8671                 unsigned message_len;
8672
8673                 p = message;
8674                 while (*p && *p == ' ') p++;
8675
8676                 message_len = strlen( p );
8677
8678                 /* [AS] Avoid buffer overflow */
8679                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8680                     strcat(thinkOutput, " ");
8681                     strcat(thinkOutput, p);
8682                 }
8683
8684                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8685                     strcat(programStats.movelist, " ");
8686                     strcat(programStats.movelist, p);
8687                 }
8688
8689                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8690                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8691                     DisplayMove(currentMove - 1);
8692                 }
8693                 return;
8694             }
8695         }
8696         else {
8697             buf1[0] = NULLCHAR;
8698
8699             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8700                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8701             {
8702                 ChessProgramStats cpstats;
8703
8704                 if (plyext != ' ' && plyext != '\t') {
8705                     time *= 100;
8706                 }
8707
8708                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8709                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8710                     curscore = -curscore;
8711                 }
8712
8713                 cpstats.depth = plylev;
8714                 cpstats.nodes = nodes;
8715                 cpstats.time = time;
8716                 cpstats.score = curscore;
8717                 cpstats.got_only_move = 0;
8718                 cpstats.movelist[0] = '\0';
8719
8720                 if (buf1[0] != NULLCHAR) {
8721                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8722                 }
8723
8724                 cpstats.ok_to_send = 0;
8725                 cpstats.line_is_book = 0;
8726                 cpstats.nr_moves = 0;
8727                 cpstats.moves_left = 0;
8728
8729                 SendProgramStatsToFrontend( cps, &cpstats );
8730             }
8731         }
8732     }
8733 }
8734
8735
8736 /* Parse a game score from the character string "game", and
8737    record it as the history of the current game.  The game
8738    score is NOT assumed to start from the standard position.
8739    The display is not updated in any way.
8740    */
8741 void
8742 ParseGameHistory(game)
8743      char *game;
8744 {
8745     ChessMove moveType;
8746     int fromX, fromY, toX, toY, boardIndex;
8747     char promoChar;
8748     char *p, *q;
8749     char buf[MSG_SIZ];
8750
8751     if (appData.debugMode)
8752       fprintf(debugFP, "Parsing game history: %s\n", game);
8753
8754     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8755     gameInfo.site = StrSave(appData.icsHost);
8756     gameInfo.date = PGNDate();
8757     gameInfo.round = StrSave("-");
8758
8759     /* Parse out names of players */
8760     while (*game == ' ') game++;
8761     p = buf;
8762     while (*game != ' ') *p++ = *game++;
8763     *p = NULLCHAR;
8764     gameInfo.white = StrSave(buf);
8765     while (*game == ' ') game++;
8766     p = buf;
8767     while (*game != ' ' && *game != '\n') *p++ = *game++;
8768     *p = NULLCHAR;
8769     gameInfo.black = StrSave(buf);
8770
8771     /* Parse moves */
8772     boardIndex = blackPlaysFirst ? 1 : 0;
8773     yynewstr(game);
8774     for (;;) {
8775         yyboardindex = boardIndex;
8776         moveType = (ChessMove) Myylex();
8777         switch (moveType) {
8778           case IllegalMove:             /* maybe suicide chess, etc. */
8779   if (appData.debugMode) {
8780     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8781     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8782     setbuf(debugFP, NULL);
8783   }
8784           case WhitePromotion:
8785           case BlackPromotion:
8786           case WhiteNonPromotion:
8787           case BlackNonPromotion:
8788           case NormalMove:
8789           case WhiteCapturesEnPassant:
8790           case BlackCapturesEnPassant:
8791           case WhiteKingSideCastle:
8792           case WhiteQueenSideCastle:
8793           case BlackKingSideCastle:
8794           case BlackQueenSideCastle:
8795           case WhiteKingSideCastleWild:
8796           case WhiteQueenSideCastleWild:
8797           case BlackKingSideCastleWild:
8798           case BlackQueenSideCastleWild:
8799           /* PUSH Fabien */
8800           case WhiteHSideCastleFR:
8801           case WhiteASideCastleFR:
8802           case BlackHSideCastleFR:
8803           case BlackASideCastleFR:
8804           /* POP Fabien */
8805             fromX = currentMoveString[0] - AAA;
8806             fromY = currentMoveString[1] - ONE;
8807             toX = currentMoveString[2] - AAA;
8808             toY = currentMoveString[3] - ONE;
8809             promoChar = currentMoveString[4];
8810             break;
8811           case WhiteDrop:
8812           case BlackDrop:
8813             fromX = moveType == WhiteDrop ?
8814               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8815             (int) CharToPiece(ToLower(currentMoveString[0]));
8816             fromY = DROP_RANK;
8817             toX = currentMoveString[2] - AAA;
8818             toY = currentMoveString[3] - ONE;
8819             promoChar = NULLCHAR;
8820             break;
8821           case AmbiguousMove:
8822             /* bug? */
8823             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8824   if (appData.debugMode) {
8825     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8826     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8827     setbuf(debugFP, NULL);
8828   }
8829             DisplayError(buf, 0);
8830             return;
8831           case ImpossibleMove:
8832             /* bug? */
8833             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8834   if (appData.debugMode) {
8835     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8836     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8837     setbuf(debugFP, NULL);
8838   }
8839             DisplayError(buf, 0);
8840             return;
8841           case EndOfFile:
8842             if (boardIndex < backwardMostMove) {
8843                 /* Oops, gap.  How did that happen? */
8844                 DisplayError(_("Gap in move list"), 0);
8845                 return;
8846             }
8847             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8848             if (boardIndex > forwardMostMove) {
8849                 forwardMostMove = boardIndex;
8850             }
8851             return;
8852           case ElapsedTime:
8853             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8854                 strcat(parseList[boardIndex-1], " ");
8855                 strcat(parseList[boardIndex-1], yy_text);
8856             }
8857             continue;
8858           case Comment:
8859           case PGNTag:
8860           case NAG:
8861           default:
8862             /* ignore */
8863             continue;
8864           case WhiteWins:
8865           case BlackWins:
8866           case GameIsDrawn:
8867           case GameUnfinished:
8868             if (gameMode == IcsExamining) {
8869                 if (boardIndex < backwardMostMove) {
8870                     /* Oops, gap.  How did that happen? */
8871                     return;
8872                 }
8873                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8874                 return;
8875             }
8876             gameInfo.result = moveType;
8877             p = strchr(yy_text, '{');
8878             if (p == NULL) p = strchr(yy_text, '(');
8879             if (p == NULL) {
8880                 p = yy_text;
8881                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8882             } else {
8883                 q = strchr(p, *p == '{' ? '}' : ')');
8884                 if (q != NULL) *q = NULLCHAR;
8885                 p++;
8886             }
8887             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8888             gameInfo.resultDetails = StrSave(p);
8889             continue;
8890         }
8891         if (boardIndex >= forwardMostMove &&
8892             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8893             backwardMostMove = blackPlaysFirst ? 1 : 0;
8894             return;
8895         }
8896         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8897                                  fromY, fromX, toY, toX, promoChar,
8898                                  parseList[boardIndex]);
8899         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8900         /* currentMoveString is set as a side-effect of yylex */
8901         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8902         strcat(moveList[boardIndex], "\n");
8903         boardIndex++;
8904         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8905         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8906           case MT_NONE:
8907           case MT_STALEMATE:
8908           default:
8909             break;
8910           case MT_CHECK:
8911             if(gameInfo.variant != VariantShogi)
8912                 strcat(parseList[boardIndex - 1], "+");
8913             break;
8914           case MT_CHECKMATE:
8915           case MT_STAINMATE:
8916             strcat(parseList[boardIndex - 1], "#");
8917             break;
8918         }
8919     }
8920 }
8921
8922
8923 /* Apply a move to the given board  */
8924 void
8925 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8926      int fromX, fromY, toX, toY;
8927      int promoChar;
8928      Board board;
8929 {
8930   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8931   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8932
8933     /* [HGM] compute & store e.p. status and castling rights for new position */
8934     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8935
8936       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8937       oldEP = (signed char)board[EP_STATUS];
8938       board[EP_STATUS] = EP_NONE;
8939
8940       if( board[toY][toX] != EmptySquare )
8941            board[EP_STATUS] = EP_CAPTURE;
8942
8943   if (fromY == DROP_RANK) {
8944         /* must be first */
8945         piece = board[toY][toX] = (ChessSquare) fromX;
8946   } else {
8947       int i;
8948
8949       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8950            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8951                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8952       } else
8953       if( board[fromY][fromX] == WhitePawn ) {
8954            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8955                board[EP_STATUS] = EP_PAWN_MOVE;
8956            if( toY-fromY==2) {
8957                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8958                         gameInfo.variant != VariantBerolina || toX < fromX)
8959                       board[EP_STATUS] = toX | berolina;
8960                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8961                         gameInfo.variant != VariantBerolina || toX > fromX)
8962                       board[EP_STATUS] = toX;
8963            }
8964       } else
8965       if( board[fromY][fromX] == BlackPawn ) {
8966            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8967                board[EP_STATUS] = EP_PAWN_MOVE;
8968            if( toY-fromY== -2) {
8969                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8970                         gameInfo.variant != VariantBerolina || toX < fromX)
8971                       board[EP_STATUS] = toX | berolina;
8972                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8973                         gameInfo.variant != VariantBerolina || toX > fromX)
8974                       board[EP_STATUS] = toX;
8975            }
8976        }
8977
8978        for(i=0; i<nrCastlingRights; i++) {
8979            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8980               board[CASTLING][i] == toX   && castlingRank[i] == toY
8981              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8982        }
8983
8984      if (fromX == toX && fromY == toY) return;
8985
8986      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8987      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8988      if(gameInfo.variant == VariantKnightmate)
8989          king += (int) WhiteUnicorn - (int) WhiteKing;
8990
8991     /* Code added by Tord: */
8992     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8993     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8994         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8995       board[fromY][fromX] = EmptySquare;
8996       board[toY][toX] = EmptySquare;
8997       if((toX > fromX) != (piece == WhiteRook)) {
8998         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8999       } else {
9000         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9001       }
9002     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9003                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9004       board[fromY][fromX] = EmptySquare;
9005       board[toY][toX] = EmptySquare;
9006       if((toX > fromX) != (piece == BlackRook)) {
9007         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9008       } else {
9009         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9010       }
9011     /* End of code added by Tord */
9012
9013     } else if (board[fromY][fromX] == king
9014         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9015         && toY == fromY && toX > fromX+1) {
9016         board[fromY][fromX] = EmptySquare;
9017         board[toY][toX] = king;
9018         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9019         board[fromY][BOARD_RGHT-1] = EmptySquare;
9020     } else if (board[fromY][fromX] == king
9021         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9022                && toY == fromY && toX < fromX-1) {
9023         board[fromY][fromX] = EmptySquare;
9024         board[toY][toX] = king;
9025         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9026         board[fromY][BOARD_LEFT] = EmptySquare;
9027     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9028                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9029                && toY >= BOARD_HEIGHT-promoRank
9030                ) {
9031         /* white pawn promotion */
9032         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9033         if (board[toY][toX] == EmptySquare) {
9034             board[toY][toX] = WhiteQueen;
9035         }
9036         if(gameInfo.variant==VariantBughouse ||
9037            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9038             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9039         board[fromY][fromX] = EmptySquare;
9040     } else if ((fromY == BOARD_HEIGHT-4)
9041                && (toX != fromX)
9042                && gameInfo.variant != VariantXiangqi
9043                && gameInfo.variant != VariantBerolina
9044                && (board[fromY][fromX] == WhitePawn)
9045                && (board[toY][toX] == EmptySquare)) {
9046         board[fromY][fromX] = EmptySquare;
9047         board[toY][toX] = WhitePawn;
9048         captured = board[toY - 1][toX];
9049         board[toY - 1][toX] = EmptySquare;
9050     } else if ((fromY == BOARD_HEIGHT-4)
9051                && (toX == fromX)
9052                && gameInfo.variant == VariantBerolina
9053                && (board[fromY][fromX] == WhitePawn)
9054                && (board[toY][toX] == EmptySquare)) {
9055         board[fromY][fromX] = EmptySquare;
9056         board[toY][toX] = WhitePawn;
9057         if(oldEP & EP_BEROLIN_A) {
9058                 captured = board[fromY][fromX-1];
9059                 board[fromY][fromX-1] = EmptySquare;
9060         }else{  captured = board[fromY][fromX+1];
9061                 board[fromY][fromX+1] = EmptySquare;
9062         }
9063     } else if (board[fromY][fromX] == king
9064         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9065                && toY == fromY && toX > fromX+1) {
9066         board[fromY][fromX] = EmptySquare;
9067         board[toY][toX] = king;
9068         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9069         board[fromY][BOARD_RGHT-1] = EmptySquare;
9070     } else if (board[fromY][fromX] == king
9071         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9072                && toY == fromY && toX < fromX-1) {
9073         board[fromY][fromX] = EmptySquare;
9074         board[toY][toX] = king;
9075         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9076         board[fromY][BOARD_LEFT] = EmptySquare;
9077     } else if (fromY == 7 && fromX == 3
9078                && board[fromY][fromX] == BlackKing
9079                && toY == 7 && toX == 5) {
9080         board[fromY][fromX] = EmptySquare;
9081         board[toY][toX] = BlackKing;
9082         board[fromY][7] = EmptySquare;
9083         board[toY][4] = BlackRook;
9084     } else if (fromY == 7 && fromX == 3
9085                && board[fromY][fromX] == BlackKing
9086                && toY == 7 && toX == 1) {
9087         board[fromY][fromX] = EmptySquare;
9088         board[toY][toX] = BlackKing;
9089         board[fromY][0] = EmptySquare;
9090         board[toY][2] = BlackRook;
9091     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9092                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9093                && toY < promoRank
9094                ) {
9095         /* black pawn promotion */
9096         board[toY][toX] = CharToPiece(ToLower(promoChar));
9097         if (board[toY][toX] == EmptySquare) {
9098             board[toY][toX] = BlackQueen;
9099         }
9100         if(gameInfo.variant==VariantBughouse ||
9101            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9102             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9103         board[fromY][fromX] = EmptySquare;
9104     } else if ((fromY == 3)
9105                && (toX != fromX)
9106                && gameInfo.variant != VariantXiangqi
9107                && gameInfo.variant != VariantBerolina
9108                && (board[fromY][fromX] == BlackPawn)
9109                && (board[toY][toX] == EmptySquare)) {
9110         board[fromY][fromX] = EmptySquare;
9111         board[toY][toX] = BlackPawn;
9112         captured = board[toY + 1][toX];
9113         board[toY + 1][toX] = EmptySquare;
9114     } else if ((fromY == 3)
9115                && (toX == fromX)
9116                && gameInfo.variant == VariantBerolina
9117                && (board[fromY][fromX] == BlackPawn)
9118                && (board[toY][toX] == EmptySquare)) {
9119         board[fromY][fromX] = EmptySquare;
9120         board[toY][toX] = BlackPawn;
9121         if(oldEP & EP_BEROLIN_A) {
9122                 captured = board[fromY][fromX-1];
9123                 board[fromY][fromX-1] = EmptySquare;
9124         }else{  captured = board[fromY][fromX+1];
9125                 board[fromY][fromX+1] = EmptySquare;
9126         }
9127     } else {
9128         board[toY][toX] = board[fromY][fromX];
9129         board[fromY][fromX] = EmptySquare;
9130     }
9131   }
9132
9133     if (gameInfo.holdingsWidth != 0) {
9134
9135       /* !!A lot more code needs to be written to support holdings  */
9136       /* [HGM] OK, so I have written it. Holdings are stored in the */
9137       /* penultimate board files, so they are automaticlly stored   */
9138       /* in the game history.                                       */
9139       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9140                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9141         /* Delete from holdings, by decreasing count */
9142         /* and erasing image if necessary            */
9143         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9144         if(p < (int) BlackPawn) { /* white drop */
9145              p -= (int)WhitePawn;
9146                  p = PieceToNumber((ChessSquare)p);
9147              if(p >= gameInfo.holdingsSize) p = 0;
9148              if(--board[p][BOARD_WIDTH-2] <= 0)
9149                   board[p][BOARD_WIDTH-1] = EmptySquare;
9150              if((int)board[p][BOARD_WIDTH-2] < 0)
9151                         board[p][BOARD_WIDTH-2] = 0;
9152         } else {                  /* black drop */
9153              p -= (int)BlackPawn;
9154                  p = PieceToNumber((ChessSquare)p);
9155              if(p >= gameInfo.holdingsSize) p = 0;
9156              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9157                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9158              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9159                         board[BOARD_HEIGHT-1-p][1] = 0;
9160         }
9161       }
9162       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9163           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9164         /* [HGM] holdings: Add to holdings, if holdings exist */
9165         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9166                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9167                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9168         }
9169         p = (int) captured;
9170         if (p >= (int) BlackPawn) {
9171           p -= (int)BlackPawn;
9172           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9173                   /* in Shogi restore piece to its original  first */
9174                   captured = (ChessSquare) (DEMOTED captured);
9175                   p = DEMOTED p;
9176           }
9177           p = PieceToNumber((ChessSquare)p);
9178           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9179           board[p][BOARD_WIDTH-2]++;
9180           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9181         } else {
9182           p -= (int)WhitePawn;
9183           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9184                   captured = (ChessSquare) (DEMOTED captured);
9185                   p = DEMOTED p;
9186           }
9187           p = PieceToNumber((ChessSquare)p);
9188           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9189           board[BOARD_HEIGHT-1-p][1]++;
9190           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9191         }
9192       }
9193     } else if (gameInfo.variant == VariantAtomic) {
9194       if (captured != EmptySquare) {
9195         int y, x;
9196         for (y = toY-1; y <= toY+1; y++) {
9197           for (x = toX-1; x <= toX+1; x++) {
9198             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9199                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9200               board[y][x] = EmptySquare;
9201             }
9202           }
9203         }
9204         board[toY][toX] = EmptySquare;
9205       }
9206     }
9207     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9208         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9209     } else
9210     if(promoChar == '+') {
9211         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9212         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9213     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9214         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9215     }
9216     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9217                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9218         // [HGM] superchess: take promotion piece out of holdings
9219         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9220         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9221             if(!--board[k][BOARD_WIDTH-2])
9222                 board[k][BOARD_WIDTH-1] = EmptySquare;
9223         } else {
9224             if(!--board[BOARD_HEIGHT-1-k][1])
9225                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9226         }
9227     }
9228
9229 }
9230
9231 /* Updates forwardMostMove */
9232 void
9233 MakeMove(fromX, fromY, toX, toY, promoChar)
9234      int fromX, fromY, toX, toY;
9235      int promoChar;
9236 {
9237 //    forwardMostMove++; // [HGM] bare: moved downstream
9238
9239     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9240         int timeLeft; static int lastLoadFlag=0; int king, piece;
9241         piece = boards[forwardMostMove][fromY][fromX];
9242         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9243         if(gameInfo.variant == VariantKnightmate)
9244             king += (int) WhiteUnicorn - (int) WhiteKing;
9245         if(forwardMostMove == 0) {
9246             if(blackPlaysFirst)
9247                 fprintf(serverMoves, "%s;", second.tidy);
9248             fprintf(serverMoves, "%s;", first.tidy);
9249             if(!blackPlaysFirst)
9250                 fprintf(serverMoves, "%s;", second.tidy);
9251         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9252         lastLoadFlag = loadFlag;
9253         // print base move
9254         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9255         // print castling suffix
9256         if( toY == fromY && piece == king ) {
9257             if(toX-fromX > 1)
9258                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9259             if(fromX-toX >1)
9260                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9261         }
9262         // e.p. suffix
9263         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9264              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9265              boards[forwardMostMove][toY][toX] == EmptySquare
9266              && fromX != toX && fromY != toY)
9267                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9268         // promotion suffix
9269         if(promoChar != NULLCHAR)
9270                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9271         if(!loadFlag) {
9272             fprintf(serverMoves, "/%d/%d",
9273                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9274             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9275             else                      timeLeft = blackTimeRemaining/1000;
9276             fprintf(serverMoves, "/%d", timeLeft);
9277         }
9278         fflush(serverMoves);
9279     }
9280
9281     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9282       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9283                         0, 1);
9284       return;
9285     }
9286     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9287     if (commentList[forwardMostMove+1] != NULL) {
9288         free(commentList[forwardMostMove+1]);
9289         commentList[forwardMostMove+1] = NULL;
9290     }
9291     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9292     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9293     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9294     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9295     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9296     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9297     gameInfo.result = GameUnfinished;
9298     if (gameInfo.resultDetails != NULL) {
9299         free(gameInfo.resultDetails);
9300         gameInfo.resultDetails = NULL;
9301     }
9302     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9303                               moveList[forwardMostMove - 1]);
9304     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9305                              PosFlags(forwardMostMove - 1),
9306                              fromY, fromX, toY, toX, promoChar,
9307                              parseList[forwardMostMove - 1]);
9308     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9309       case MT_NONE:
9310       case MT_STALEMATE:
9311       default:
9312         break;
9313       case MT_CHECK:
9314         if(gameInfo.variant != VariantShogi)
9315             strcat(parseList[forwardMostMove - 1], "+");
9316         break;
9317       case MT_CHECKMATE:
9318       case MT_STAINMATE:
9319         strcat(parseList[forwardMostMove - 1], "#");
9320         break;
9321     }
9322     if (appData.debugMode) {
9323         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9324     }
9325
9326 }
9327
9328 /* Updates currentMove if not pausing */
9329 void
9330 ShowMove(fromX, fromY, toX, toY)
9331 {
9332     int instant = (gameMode == PlayFromGameFile) ?
9333         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9334     if(appData.noGUI) return;
9335     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9336         if (!instant) {
9337             if (forwardMostMove == currentMove + 1) {
9338                 AnimateMove(boards[forwardMostMove - 1],
9339                             fromX, fromY, toX, toY);
9340             }
9341             if (appData.highlightLastMove) {
9342                 SetHighlights(fromX, fromY, toX, toY);
9343             }
9344         }
9345         currentMove = forwardMostMove;
9346     }
9347
9348     if (instant) return;
9349
9350     DisplayMove(currentMove - 1);
9351     DrawPosition(FALSE, boards[currentMove]);
9352     DisplayBothClocks();
9353     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9354     DisplayBook(currentMove);
9355 }
9356
9357 void SendEgtPath(ChessProgramState *cps)
9358 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9359         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9360
9361         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9362
9363         while(*p) {
9364             char c, *q = name+1, *r, *s;
9365
9366             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9367             while(*p && *p != ',') *q++ = *p++;
9368             *q++ = ':'; *q = 0;
9369             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9370                 strcmp(name, ",nalimov:") == 0 ) {
9371                 // take nalimov path from the menu-changeable option first, if it is defined
9372               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9373                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9374             } else
9375             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9376                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9377                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9378                 s = r = StrStr(s, ":") + 1; // beginning of path info
9379                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9380                 c = *r; *r = 0;             // temporarily null-terminate path info
9381                     *--q = 0;               // strip of trailig ':' from name
9382                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9383                 *r = c;
9384                 SendToProgram(buf,cps);     // send egtbpath command for this format
9385             }
9386             if(*p == ',') p++; // read away comma to position for next format name
9387         }
9388 }
9389
9390 void
9391 InitChessProgram(cps, setup)
9392      ChessProgramState *cps;
9393      int setup; /* [HGM] needed to setup FRC opening position */
9394 {
9395     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9396     if (appData.noChessProgram) return;
9397     hintRequested = FALSE;
9398     bookRequested = FALSE;
9399
9400     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9401     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9402     if(cps->memSize) { /* [HGM] memory */
9403       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9404         SendToProgram(buf, cps);
9405     }
9406     SendEgtPath(cps); /* [HGM] EGT */
9407     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9408       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9409         SendToProgram(buf, cps);
9410     }
9411
9412     SendToProgram(cps->initString, cps);
9413     if (gameInfo.variant != VariantNormal &&
9414         gameInfo.variant != VariantLoadable
9415         /* [HGM] also send variant if board size non-standard */
9416         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9417                                             ) {
9418       char *v = VariantName(gameInfo.variant);
9419       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9420         /* [HGM] in protocol 1 we have to assume all variants valid */
9421         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9422         DisplayFatalError(buf, 0, 1);
9423         return;
9424       }
9425
9426       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9427       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9428       if( gameInfo.variant == VariantXiangqi )
9429            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9430       if( gameInfo.variant == VariantShogi )
9431            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9432       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9433            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9434       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9435           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9436            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9437       if( gameInfo.variant == VariantCourier )
9438            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9439       if( gameInfo.variant == VariantSuper )
9440            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9441       if( gameInfo.variant == VariantGreat )
9442            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9443       if( gameInfo.variant == VariantSChess )
9444            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9445
9446       if(overruled) {
9447         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9448                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9449            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9450            if(StrStr(cps->variants, b) == NULL) {
9451                // specific sized variant not known, check if general sizing allowed
9452                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9453                    if(StrStr(cps->variants, "boardsize") == NULL) {
9454                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9455                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9456                        DisplayFatalError(buf, 0, 1);
9457                        return;
9458                    }
9459                    /* [HGM] here we really should compare with the maximum supported board size */
9460                }
9461            }
9462       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9463       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9464       SendToProgram(buf, cps);
9465     }
9466     currentlyInitializedVariant = gameInfo.variant;
9467
9468     /* [HGM] send opening position in FRC to first engine */
9469     if(setup) {
9470           SendToProgram("force\n", cps);
9471           SendBoard(cps, 0);
9472           /* engine is now in force mode! Set flag to wake it up after first move. */
9473           setboardSpoiledMachineBlack = 1;
9474     }
9475
9476     if (cps->sendICS) {
9477       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9478       SendToProgram(buf, cps);
9479     }
9480     cps->maybeThinking = FALSE;
9481     cps->offeredDraw = 0;
9482     if (!appData.icsActive) {
9483         SendTimeControl(cps, movesPerSession, timeControl,
9484                         timeIncrement, appData.searchDepth,
9485                         searchTime);
9486     }
9487     if (appData.showThinking
9488         // [HGM] thinking: four options require thinking output to be sent
9489         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9490                                 ) {
9491         SendToProgram("post\n", cps);
9492     }
9493     SendToProgram("hard\n", cps);
9494     if (!appData.ponderNextMove) {
9495         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9496            it without being sure what state we are in first.  "hard"
9497            is not a toggle, so that one is OK.
9498          */
9499         SendToProgram("easy\n", cps);
9500     }
9501     if (cps->usePing) {
9502       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9503       SendToProgram(buf, cps);
9504     }
9505     cps->initDone = TRUE;
9506     ClearEngineOutputPane(cps == &second);
9507 }
9508
9509
9510 void
9511 StartChessProgram(cps)
9512      ChessProgramState *cps;
9513 {
9514     char buf[MSG_SIZ];
9515     int err;
9516
9517     if (appData.noChessProgram) return;
9518     cps->initDone = FALSE;
9519
9520     if (strcmp(cps->host, "localhost") == 0) {
9521         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9522     } else if (*appData.remoteShell == NULLCHAR) {
9523         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9524     } else {
9525         if (*appData.remoteUser == NULLCHAR) {
9526           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9527                     cps->program);
9528         } else {
9529           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9530                     cps->host, appData.remoteUser, cps->program);
9531         }
9532         err = StartChildProcess(buf, "", &cps->pr);
9533     }
9534
9535     if (err != 0) {
9536       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9537         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9538         if(cps != &first) return;
9539         appData.noChessProgram = TRUE;
9540         ThawUI();
9541         SetNCPMode();
9542 //      DisplayFatalError(buf, err, 1);
9543 //      cps->pr = NoProc;
9544 //      cps->isr = NULL;
9545         return;
9546     }
9547
9548     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9549     if (cps->protocolVersion > 1) {
9550       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9551       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9552       cps->comboCnt = 0;  //                and values of combo boxes
9553       SendToProgram(buf, cps);
9554     } else {
9555       SendToProgram("xboard\n", cps);
9556     }
9557 }
9558
9559 void
9560 TwoMachinesEventIfReady P((void))
9561 {
9562   static int curMess = 0;
9563   if (first.lastPing != first.lastPong) {
9564     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9565     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9566     return;
9567   }
9568   if (second.lastPing != second.lastPong) {
9569     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9570     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9571     return;
9572   }
9573   DisplayMessage("", ""); curMess = 0;
9574   ThawUI();
9575   TwoMachinesEvent();
9576 }
9577
9578 char *
9579 MakeName(char *template)
9580 {
9581     time_t clock;
9582     struct tm *tm;
9583     static char buf[MSG_SIZ];
9584     char *p = buf;
9585     int i;
9586
9587     clock = time((time_t *)NULL);
9588     tm = localtime(&clock);
9589
9590     while(*p++ = *template++) if(p[-1] == '%') {
9591         switch(*template++) {
9592           case 0:   *p = 0; return buf;
9593           case 'Y': i = tm->tm_year+1900; break;
9594           case 'y': i = tm->tm_year-100; break;
9595           case 'M': i = tm->tm_mon+1; break;
9596           case 'd': i = tm->tm_mday; break;
9597           case 'h': i = tm->tm_hour; break;
9598           case 'm': i = tm->tm_min; break;
9599           case 's': i = tm->tm_sec; break;
9600           default:  i = 0;
9601         }
9602         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9603     }
9604     return buf;
9605 }
9606
9607 int
9608 CountPlayers(char *p)
9609 {
9610     int n = 0;
9611     while(p = strchr(p, '\n')) p++, n++; // count participants
9612     return n;
9613 }
9614
9615 FILE *
9616 WriteTourneyFile(char *results)
9617 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9618     FILE *f = fopen(appData.tourneyFile, "w");
9619     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9620         // create a file with tournament description
9621         fprintf(f, "-participants {%s}\n", appData.participants);
9622         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9623         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9624         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9625         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9626         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9627         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9628         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9629         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9630         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9631         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9632         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9633         if(searchTime > 0)
9634                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9635         else {
9636                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9637                 fprintf(f, "-tc %s\n", appData.timeControl);
9638                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9639         }
9640         fprintf(f, "-results \"%s\"\n", results);
9641     }
9642     return f;
9643 }
9644
9645 int
9646 CreateTourney(char *name)
9647 {
9648         FILE *f;
9649         if(name[0] == NULLCHAR) {
9650             if(appData.participants[0])
9651                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9652             return 0;
9653         }
9654         f = fopen(name, "r");
9655         if(f) { // file exists
9656             ASSIGN(appData.tourneyFile, name);
9657             ParseArgsFromFile(f); // parse it
9658         } else {
9659             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9660             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9661                 DisplayError(_("Not enough participants"), 0);
9662                 return 0;
9663             }
9664             ASSIGN(appData.tourneyFile, name);
9665             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9666             if((f = WriteTourneyFile("")) == NULL) return 0;
9667         }
9668         fclose(f);
9669         appData.noChessProgram = FALSE;
9670         appData.clockMode = TRUE;
9671         SetGNUMode();
9672         return 1;
9673 }
9674
9675 #define MAXENGINES 1000
9676 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9677
9678 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9679 {
9680     char buf[MSG_SIZ], *p, *q;
9681     int i=1;
9682     while(*names) {
9683         p = names; q = buf;
9684         while(*p && *p != '\n') *q++ = *p++;
9685         *q = 0;
9686         if(engineList[i]) free(engineList[i]);
9687         engineList[i] = strdup(buf);
9688         if(*p == '\n') p++;
9689         TidyProgramName(engineList[i], "localhost", buf);
9690         if(engineMnemonic[i]) free(engineMnemonic[i]);
9691         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9692             strcat(buf, " (");
9693             sscanf(q + 8, "%s", buf + strlen(buf));
9694             strcat(buf, ")");
9695         }
9696         engineMnemonic[i] = strdup(buf);
9697         names = p; i++;
9698       if(i > MAXENGINES - 2) break;
9699     }
9700     engineList[i] = NULL;
9701 }
9702
9703 // following implemented as macro to avoid type limitations
9704 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9705
9706 void SwapEngines(int n)
9707 {   // swap settings for first engine and other engine (so far only some selected options)
9708     int h;
9709     char *p;
9710     if(n == 0) return;
9711     SWAP(directory, p)
9712     SWAP(chessProgram, p)
9713     SWAP(isUCI, h)
9714     SWAP(hasOwnBookUCI, h)
9715     SWAP(protocolVersion, h)
9716     SWAP(reuse, h)
9717     SWAP(scoreIsAbsolute, h)
9718     SWAP(timeOdds, h)
9719     SWAP(logo, p)
9720     SWAP(pgnName, p)
9721 }
9722
9723 void
9724 SetPlayer(int player)
9725 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9726     int i;
9727     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9728     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9729     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9730     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9731     if(mnemonic[i]) {
9732         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9733         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9734         ParseArgsFromString(buf);
9735     }
9736     free(engineName);
9737 }
9738
9739 int
9740 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9741 {   // determine players from game number
9742     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9743
9744     if(appData.tourneyType == 0) {
9745         roundsPerCycle = (nPlayers - 1) | 1;
9746         pairingsPerRound = nPlayers / 2;
9747     } else if(appData.tourneyType > 0) {
9748         roundsPerCycle = nPlayers - appData.tourneyType;
9749         pairingsPerRound = appData.tourneyType;
9750     }
9751     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9752     gamesPerCycle = gamesPerRound * roundsPerCycle;
9753     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9754     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9755     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9756     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9757     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9758     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9759
9760     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9761     if(appData.roundSync) *syncInterval = gamesPerRound;
9762
9763     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9764
9765     if(appData.tourneyType == 0) {
9766         if(curPairing == (nPlayers-1)/2 ) {
9767             *whitePlayer = curRound;
9768             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9769         } else {
9770             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9771             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9772             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9773             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9774         }
9775     } else if(appData.tourneyType > 0) {
9776         *whitePlayer = curPairing;
9777         *blackPlayer = curRound + appData.tourneyType;
9778     }
9779
9780     // take care of white/black alternation per round. 
9781     // For cycles and games this is already taken care of by default, derived from matchGame!
9782     return curRound & 1;
9783 }
9784
9785 int
9786 NextTourneyGame(int nr, int *swapColors)
9787 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9788     char *p, *q;
9789     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9790     FILE *tf;
9791     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9792     tf = fopen(appData.tourneyFile, "r");
9793     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9794     ParseArgsFromFile(tf); fclose(tf);
9795     InitTimeControls(); // TC might be altered from tourney file
9796
9797     nPlayers = CountPlayers(appData.participants); // count participants
9798     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9799     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9800
9801     if(syncInterval) {
9802         p = q = appData.results;
9803         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9804         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9805             DisplayMessage(_("Waiting for other game(s)"),"");
9806             waitingForGame = TRUE;
9807             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9808             return 0;
9809         }
9810         waitingForGame = FALSE;
9811     }
9812
9813     if(appData.tourneyType < 0) {
9814         if(nr>=0 && !pairingReceived) {
9815             char buf[1<<16];
9816             if(pairing.pr == NoProc) {
9817                 if(!appData.pairingEngine[0]) {
9818                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9819                     return 0;
9820                 }
9821                 StartChessProgram(&pairing); // starts the pairing engine
9822             }
9823             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9824             SendToProgram(buf, &pairing);
9825             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9826             SendToProgram(buf, &pairing);
9827             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9828         }
9829         pairingReceived = 0;                              // ... so we continue here 
9830         *swapColors = 0;
9831         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9832         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9833         matchGame = 1; roundNr = nr / syncInterval + 1;
9834     }
9835
9836     if(first.pr != NoProc) return 1; // engines already loaded
9837
9838     // redefine engines, engine dir, etc.
9839     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9840     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9841     SwapEngines(1);
9842     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9843     SwapEngines(1);         // and make that valid for second engine by swapping
9844     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9845     InitEngine(&second, 1);
9846     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9847     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9848     return 1;
9849 }
9850
9851 void
9852 NextMatchGame()
9853 {   // performs game initialization that does not invoke engines, and then tries to start the game
9854     int firstWhite, swapColors = 0;
9855     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9856     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9857     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9858     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9859     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9860     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9861     Reset(FALSE, first.pr != NoProc);
9862     appData.noChessProgram = FALSE;
9863     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9864     TwoMachinesEvent();
9865 }
9866
9867 void UserAdjudicationEvent( int result )
9868 {
9869     ChessMove gameResult = GameIsDrawn;
9870
9871     if( result > 0 ) {
9872         gameResult = WhiteWins;
9873     }
9874     else if( result < 0 ) {
9875         gameResult = BlackWins;
9876     }
9877
9878     if( gameMode == TwoMachinesPlay ) {
9879         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9880     }
9881 }
9882
9883
9884 // [HGM] save: calculate checksum of game to make games easily identifiable
9885 int StringCheckSum(char *s)
9886 {
9887         int i = 0;
9888         if(s==NULL) return 0;
9889         while(*s) i = i*259 + *s++;
9890         return i;
9891 }
9892
9893 int GameCheckSum()
9894 {
9895         int i, sum=0;
9896         for(i=backwardMostMove; i<forwardMostMove; i++) {
9897                 sum += pvInfoList[i].depth;
9898                 sum += StringCheckSum(parseList[i]);
9899                 sum += StringCheckSum(commentList[i]);
9900                 sum *= 261;
9901         }
9902         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9903         return sum + StringCheckSum(commentList[i]);
9904 } // end of save patch
9905
9906 void
9907 GameEnds(result, resultDetails, whosays)
9908      ChessMove result;
9909      char *resultDetails;
9910      int whosays;
9911 {
9912     GameMode nextGameMode;
9913     int isIcsGame;
9914     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9915
9916     if(endingGame) return; /* [HGM] crash: forbid recursion */
9917     endingGame = 1;
9918     if(twoBoards) { // [HGM] dual: switch back to one board
9919         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9920         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9921     }
9922     if (appData.debugMode) {
9923       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9924               result, resultDetails ? resultDetails : "(null)", whosays);
9925     }
9926
9927     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9928
9929     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9930         /* If we are playing on ICS, the server decides when the
9931            game is over, but the engine can offer to draw, claim
9932            a draw, or resign.
9933          */
9934 #if ZIPPY
9935         if (appData.zippyPlay && first.initDone) {
9936             if (result == GameIsDrawn) {
9937                 /* In case draw still needs to be claimed */
9938                 SendToICS(ics_prefix);
9939                 SendToICS("draw\n");
9940             } else if (StrCaseStr(resultDetails, "resign")) {
9941                 SendToICS(ics_prefix);
9942                 SendToICS("resign\n");
9943             }
9944         }
9945 #endif
9946         endingGame = 0; /* [HGM] crash */
9947         return;
9948     }
9949
9950     /* If we're loading the game from a file, stop */
9951     if (whosays == GE_FILE) {
9952       (void) StopLoadGameTimer();
9953       gameFileFP = NULL;
9954     }
9955
9956     /* Cancel draw offers */
9957     first.offeredDraw = second.offeredDraw = 0;
9958
9959     /* If this is an ICS game, only ICS can really say it's done;
9960        if not, anyone can. */
9961     isIcsGame = (gameMode == IcsPlayingWhite ||
9962                  gameMode == IcsPlayingBlack ||
9963                  gameMode == IcsObserving    ||
9964                  gameMode == IcsExamining);
9965
9966     if (!isIcsGame || whosays == GE_ICS) {
9967         /* OK -- not an ICS game, or ICS said it was done */
9968         StopClocks();
9969         if (!isIcsGame && !appData.noChessProgram)
9970           SetUserThinkingEnables();
9971
9972         /* [HGM] if a machine claims the game end we verify this claim */
9973         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9974             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9975                 char claimer;
9976                 ChessMove trueResult = (ChessMove) -1;
9977
9978                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9979                                             first.twoMachinesColor[0] :
9980                                             second.twoMachinesColor[0] ;
9981
9982                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9983                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9984                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9985                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9986                 } else
9987                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9988                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9989                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9990                 } else
9991                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9992                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9993                 }
9994
9995                 // now verify win claims, but not in drop games, as we don't understand those yet
9996                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9997                                                  || gameInfo.variant == VariantGreat) &&
9998                     (result == WhiteWins && claimer == 'w' ||
9999                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10000                       if (appData.debugMode) {
10001                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10002                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10003                       }
10004                       if(result != trueResult) {
10005                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10006                               result = claimer == 'w' ? BlackWins : WhiteWins;
10007                               resultDetails = buf;
10008                       }
10009                 } else
10010                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10011                     && (forwardMostMove <= backwardMostMove ||
10012                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10013                         (claimer=='b')==(forwardMostMove&1))
10014                                                                                   ) {
10015                       /* [HGM] verify: draws that were not flagged are false claims */
10016                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10017                       result = claimer == 'w' ? BlackWins : WhiteWins;
10018                       resultDetails = buf;
10019                 }
10020                 /* (Claiming a loss is accepted no questions asked!) */
10021             }
10022             /* [HGM] bare: don't allow bare King to win */
10023             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10024                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10025                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10026                && result != GameIsDrawn)
10027             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10028                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10029                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10030                         if(p >= 0 && p <= (int)WhiteKing) k++;
10031                 }
10032                 if (appData.debugMode) {
10033                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10034                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10035                 }
10036                 if(k <= 1) {
10037                         result = GameIsDrawn;
10038                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10039                         resultDetails = buf;
10040                 }
10041             }
10042         }
10043
10044
10045         if(serverMoves != NULL && !loadFlag) { char c = '=';
10046             if(result==WhiteWins) c = '+';
10047             if(result==BlackWins) c = '-';
10048             if(resultDetails != NULL)
10049                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10050         }
10051         if (resultDetails != NULL) {
10052             gameInfo.result = result;
10053             gameInfo.resultDetails = StrSave(resultDetails);
10054
10055             /* display last move only if game was not loaded from file */
10056             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10057                 DisplayMove(currentMove - 1);
10058
10059             if (forwardMostMove != 0) {
10060                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10061                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10062                                                                 ) {
10063                     if (*appData.saveGameFile != NULLCHAR) {
10064                         SaveGameToFile(appData.saveGameFile, TRUE);
10065                     } else if (appData.autoSaveGames) {
10066                         AutoSaveGame();
10067                     }
10068                     if (*appData.savePositionFile != NULLCHAR) {
10069                         SavePositionToFile(appData.savePositionFile);
10070                     }
10071                 }
10072             }
10073
10074             /* Tell program how game ended in case it is learning */
10075             /* [HGM] Moved this to after saving the PGN, just in case */
10076             /* engine died and we got here through time loss. In that */
10077             /* case we will get a fatal error writing the pipe, which */
10078             /* would otherwise lose us the PGN.                       */
10079             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10080             /* output during GameEnds should never be fatal anymore   */
10081             if (gameMode == MachinePlaysWhite ||
10082                 gameMode == MachinePlaysBlack ||
10083                 gameMode == TwoMachinesPlay ||
10084                 gameMode == IcsPlayingWhite ||
10085                 gameMode == IcsPlayingBlack ||
10086                 gameMode == BeginningOfGame) {
10087                 char buf[MSG_SIZ];
10088                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10089                         resultDetails);
10090                 if (first.pr != NoProc) {
10091                     SendToProgram(buf, &first);
10092                 }
10093                 if (second.pr != NoProc &&
10094                     gameMode == TwoMachinesPlay) {
10095                     SendToProgram(buf, &second);
10096                 }
10097             }
10098         }
10099
10100         if (appData.icsActive) {
10101             if (appData.quietPlay &&
10102                 (gameMode == IcsPlayingWhite ||
10103                  gameMode == IcsPlayingBlack)) {
10104                 SendToICS(ics_prefix);
10105                 SendToICS("set shout 1\n");
10106             }
10107             nextGameMode = IcsIdle;
10108             ics_user_moved = FALSE;
10109             /* clean up premove.  It's ugly when the game has ended and the
10110              * premove highlights are still on the board.
10111              */
10112             if (gotPremove) {
10113               gotPremove = FALSE;
10114               ClearPremoveHighlights();
10115               DrawPosition(FALSE, boards[currentMove]);
10116             }
10117             if (whosays == GE_ICS) {
10118                 switch (result) {
10119                 case WhiteWins:
10120                     if (gameMode == IcsPlayingWhite)
10121                         PlayIcsWinSound();
10122                     else if(gameMode == IcsPlayingBlack)
10123                         PlayIcsLossSound();
10124                     break;
10125                 case BlackWins:
10126                     if (gameMode == IcsPlayingBlack)
10127                         PlayIcsWinSound();
10128                     else if(gameMode == IcsPlayingWhite)
10129                         PlayIcsLossSound();
10130                     break;
10131                 case GameIsDrawn:
10132                     PlayIcsDrawSound();
10133                     break;
10134                 default:
10135                     PlayIcsUnfinishedSound();
10136                 }
10137             }
10138         } else if (gameMode == EditGame ||
10139                    gameMode == PlayFromGameFile ||
10140                    gameMode == AnalyzeMode ||
10141                    gameMode == AnalyzeFile) {
10142             nextGameMode = gameMode;
10143         } else {
10144             nextGameMode = EndOfGame;
10145         }
10146         pausing = FALSE;
10147         ModeHighlight();
10148     } else {
10149         nextGameMode = gameMode;
10150     }
10151
10152     if (appData.noChessProgram) {
10153         gameMode = nextGameMode;
10154         ModeHighlight();
10155         endingGame = 0; /* [HGM] crash */
10156         return;
10157     }
10158
10159     if (first.reuse) {
10160         /* Put first chess program into idle state */
10161         if (first.pr != NoProc &&
10162             (gameMode == MachinePlaysWhite ||
10163              gameMode == MachinePlaysBlack ||
10164              gameMode == TwoMachinesPlay ||
10165              gameMode == IcsPlayingWhite ||
10166              gameMode == IcsPlayingBlack ||
10167              gameMode == BeginningOfGame)) {
10168             SendToProgram("force\n", &first);
10169             if (first.usePing) {
10170               char buf[MSG_SIZ];
10171               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10172               SendToProgram(buf, &first);
10173             }
10174         }
10175     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10176         /* Kill off first chess program */
10177         if (first.isr != NULL)
10178           RemoveInputSource(first.isr);
10179         first.isr = NULL;
10180
10181         if (first.pr != NoProc) {
10182             ExitAnalyzeMode();
10183             DoSleep( appData.delayBeforeQuit );
10184             SendToProgram("quit\n", &first);
10185             DoSleep( appData.delayAfterQuit );
10186             DestroyChildProcess(first.pr, first.useSigterm);
10187         }
10188         first.pr = NoProc;
10189     }
10190     if (second.reuse) {
10191         /* Put second chess program into idle state */
10192         if (second.pr != NoProc &&
10193             gameMode == TwoMachinesPlay) {
10194             SendToProgram("force\n", &second);
10195             if (second.usePing) {
10196               char buf[MSG_SIZ];
10197               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10198               SendToProgram(buf, &second);
10199             }
10200         }
10201     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10202         /* Kill off second chess program */
10203         if (second.isr != NULL)
10204           RemoveInputSource(second.isr);
10205         second.isr = NULL;
10206
10207         if (second.pr != NoProc) {
10208             DoSleep( appData.delayBeforeQuit );
10209             SendToProgram("quit\n", &second);
10210             DoSleep( appData.delayAfterQuit );
10211             DestroyChildProcess(second.pr, second.useSigterm);
10212         }
10213         second.pr = NoProc;
10214     }
10215
10216     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10217         char resChar = '=';
10218         switch (result) {
10219         case WhiteWins:
10220           resChar = '+';
10221           if (first.twoMachinesColor[0] == 'w') {
10222             first.matchWins++;
10223           } else {
10224             second.matchWins++;
10225           }
10226           break;
10227         case BlackWins:
10228           resChar = '-';
10229           if (first.twoMachinesColor[0] == 'b') {
10230             first.matchWins++;
10231           } else {
10232             second.matchWins++;
10233           }
10234           break;
10235         case GameUnfinished:
10236           resChar = ' ';
10237         default:
10238           break;
10239         }
10240
10241         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10242         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10243             ReserveGame(nextGame, resChar); // sets nextGame
10244             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10245             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10246         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10247
10248         if (nextGame <= appData.matchGames && !abortMatch) {
10249             gameMode = nextGameMode;
10250             matchGame = nextGame; // this will be overruled in tourney mode!
10251             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10252             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10253             endingGame = 0; /* [HGM] crash */
10254             return;
10255         } else {
10256             gameMode = nextGameMode;
10257             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10258                      first.tidy, second.tidy,
10259                      first.matchWins, second.matchWins,
10260                      appData.matchGames - (first.matchWins + second.matchWins));
10261             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10262             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10263             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10264                 first.twoMachinesColor = "black\n";
10265                 second.twoMachinesColor = "white\n";
10266             } else {
10267                 first.twoMachinesColor = "white\n";
10268                 second.twoMachinesColor = "black\n";
10269             }
10270         }
10271     }
10272     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10273         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10274       ExitAnalyzeMode();
10275     gameMode = nextGameMode;
10276     ModeHighlight();
10277     endingGame = 0;  /* [HGM] crash */
10278     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10279         if(matchMode == TRUE) { // match through command line: exit with or without popup
10280             if(ranking) {
10281                 ToNrEvent(forwardMostMove);
10282                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10283                 else ExitEvent(0);
10284             } else DisplayFatalError(buf, 0, 0);
10285         } else { // match through menu; just stop, with or without popup
10286             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10287             ModeHighlight();
10288             if(ranking){
10289                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10290             } else DisplayNote(buf);
10291       }
10292       if(ranking) free(ranking);
10293     }
10294 }
10295
10296 /* Assumes program was just initialized (initString sent).
10297    Leaves program in force mode. */
10298 void
10299 FeedMovesToProgram(cps, upto)
10300      ChessProgramState *cps;
10301      int upto;
10302 {
10303     int i;
10304
10305     if (appData.debugMode)
10306       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10307               startedFromSetupPosition ? "position and " : "",
10308               backwardMostMove, upto, cps->which);
10309     if(currentlyInitializedVariant != gameInfo.variant) {
10310       char buf[MSG_SIZ];
10311         // [HGM] variantswitch: make engine aware of new variant
10312         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10313                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10314         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10315         SendToProgram(buf, cps);
10316         currentlyInitializedVariant = gameInfo.variant;
10317     }
10318     SendToProgram("force\n", cps);
10319     if (startedFromSetupPosition) {
10320         SendBoard(cps, backwardMostMove);
10321     if (appData.debugMode) {
10322         fprintf(debugFP, "feedMoves\n");
10323     }
10324     }
10325     for (i = backwardMostMove; i < upto; i++) {
10326         SendMoveToProgram(i, cps);
10327     }
10328 }
10329
10330
10331 int
10332 ResurrectChessProgram()
10333 {
10334      /* The chess program may have exited.
10335         If so, restart it and feed it all the moves made so far. */
10336     static int doInit = 0;
10337
10338     if (appData.noChessProgram) return 1;
10339
10340     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10341         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10342         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10343         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10344     } else {
10345         if (first.pr != NoProc) return 1;
10346         StartChessProgram(&first);
10347     }
10348     InitChessProgram(&first, FALSE);
10349     FeedMovesToProgram(&first, currentMove);
10350
10351     if (!first.sendTime) {
10352         /* can't tell gnuchess what its clock should read,
10353            so we bow to its notion. */
10354         ResetClocks();
10355         timeRemaining[0][currentMove] = whiteTimeRemaining;
10356         timeRemaining[1][currentMove] = blackTimeRemaining;
10357     }
10358
10359     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10360                 appData.icsEngineAnalyze) && first.analysisSupport) {
10361       SendToProgram("analyze\n", &first);
10362       first.analyzing = TRUE;
10363     }
10364     return 1;
10365 }
10366
10367 /*
10368  * Button procedures
10369  */
10370 void
10371 Reset(redraw, init)
10372      int redraw, init;
10373 {
10374     int i;
10375
10376     if (appData.debugMode) {
10377         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10378                 redraw, init, gameMode);
10379     }
10380     CleanupTail(); // [HGM] vari: delete any stored variations
10381     pausing = pauseExamInvalid = FALSE;
10382     startedFromSetupPosition = blackPlaysFirst = FALSE;
10383     firstMove = TRUE;
10384     whiteFlag = blackFlag = FALSE;
10385     userOfferedDraw = FALSE;
10386     hintRequested = bookRequested = FALSE;
10387     first.maybeThinking = FALSE;
10388     second.maybeThinking = FALSE;
10389     first.bookSuspend = FALSE; // [HGM] book
10390     second.bookSuspend = FALSE;
10391     thinkOutput[0] = NULLCHAR;
10392     lastHint[0] = NULLCHAR;
10393     ClearGameInfo(&gameInfo);
10394     gameInfo.variant = StringToVariant(appData.variant);
10395     ics_user_moved = ics_clock_paused = FALSE;
10396     ics_getting_history = H_FALSE;
10397     ics_gamenum = -1;
10398     white_holding[0] = black_holding[0] = NULLCHAR;
10399     ClearProgramStats();
10400     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10401
10402     ResetFrontEnd();
10403     ClearHighlights();
10404     flipView = appData.flipView;
10405     ClearPremoveHighlights();
10406     gotPremove = FALSE;
10407     alarmSounded = FALSE;
10408
10409     GameEnds(EndOfFile, NULL, GE_PLAYER);
10410     if(appData.serverMovesName != NULL) {
10411         /* [HGM] prepare to make moves file for broadcasting */
10412         clock_t t = clock();
10413         if(serverMoves != NULL) fclose(serverMoves);
10414         serverMoves = fopen(appData.serverMovesName, "r");
10415         if(serverMoves != NULL) {
10416             fclose(serverMoves);
10417             /* delay 15 sec before overwriting, so all clients can see end */
10418             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10419         }
10420         serverMoves = fopen(appData.serverMovesName, "w");
10421     }
10422
10423     ExitAnalyzeMode();
10424     gameMode = BeginningOfGame;
10425     ModeHighlight();
10426     if(appData.icsActive) gameInfo.variant = VariantNormal;
10427     currentMove = forwardMostMove = backwardMostMove = 0;
10428     InitPosition(redraw);
10429     for (i = 0; i < MAX_MOVES; i++) {
10430         if (commentList[i] != NULL) {
10431             free(commentList[i]);
10432             commentList[i] = NULL;
10433         }
10434     }
10435     ResetClocks();
10436     timeRemaining[0][0] = whiteTimeRemaining;
10437     timeRemaining[1][0] = blackTimeRemaining;
10438
10439     if (first.pr == NULL) {
10440         StartChessProgram(&first);
10441     }
10442     if (init) {
10443             InitChessProgram(&first, startedFromSetupPosition);
10444     }
10445     DisplayTitle("");
10446     DisplayMessage("", "");
10447     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10448     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10449 }
10450
10451 void
10452 AutoPlayGameLoop()
10453 {
10454     for (;;) {
10455         if (!AutoPlayOneMove())
10456           return;
10457         if (matchMode || appData.timeDelay == 0)
10458           continue;
10459         if (appData.timeDelay < 0)
10460           return;
10461         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10462         break;
10463     }
10464 }
10465
10466
10467 int
10468 AutoPlayOneMove()
10469 {
10470     int fromX, fromY, toX, toY;
10471
10472     if (appData.debugMode) {
10473       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10474     }
10475
10476     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10477       return FALSE;
10478
10479     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10480       pvInfoList[currentMove].depth = programStats.depth;
10481       pvInfoList[currentMove].score = programStats.score;
10482       pvInfoList[currentMove].time  = 0;
10483       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10484     }
10485
10486     if (currentMove >= forwardMostMove) {
10487       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10488       gameMode = EditGame;
10489       ModeHighlight();
10490
10491       /* [AS] Clear current move marker at the end of a game */
10492       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10493
10494       return FALSE;
10495     }
10496
10497     toX = moveList[currentMove][2] - AAA;
10498     toY = moveList[currentMove][3] - ONE;
10499
10500     if (moveList[currentMove][1] == '@') {
10501         if (appData.highlightLastMove) {
10502             SetHighlights(-1, -1, toX, toY);
10503         }
10504     } else {
10505         fromX = moveList[currentMove][0] - AAA;
10506         fromY = moveList[currentMove][1] - ONE;
10507
10508         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10509
10510         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10511
10512         if (appData.highlightLastMove) {
10513             SetHighlights(fromX, fromY, toX, toY);
10514         }
10515     }
10516     DisplayMove(currentMove);
10517     SendMoveToProgram(currentMove++, &first);
10518     DisplayBothClocks();
10519     DrawPosition(FALSE, boards[currentMove]);
10520     // [HGM] PV info: always display, routine tests if empty
10521     DisplayComment(currentMove - 1, commentList[currentMove]);
10522     return TRUE;
10523 }
10524
10525
10526 int
10527 LoadGameOneMove(readAhead)
10528      ChessMove readAhead;
10529 {
10530     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10531     char promoChar = NULLCHAR;
10532     ChessMove moveType;
10533     char move[MSG_SIZ];
10534     char *p, *q;
10535
10536     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10537         gameMode != AnalyzeMode && gameMode != Training) {
10538         gameFileFP = NULL;
10539         return FALSE;
10540     }
10541
10542     yyboardindex = forwardMostMove;
10543     if (readAhead != EndOfFile) {
10544       moveType = readAhead;
10545     } else {
10546       if (gameFileFP == NULL)
10547           return FALSE;
10548       moveType = (ChessMove) Myylex();
10549     }
10550
10551     done = FALSE;
10552     switch (moveType) {
10553       case Comment:
10554         if (appData.debugMode)
10555           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10556         p = yy_text;
10557
10558         /* append the comment but don't display it */
10559         AppendComment(currentMove, p, FALSE);
10560         return TRUE;
10561
10562       case WhiteCapturesEnPassant:
10563       case BlackCapturesEnPassant:
10564       case WhitePromotion:
10565       case BlackPromotion:
10566       case WhiteNonPromotion:
10567       case BlackNonPromotion:
10568       case NormalMove:
10569       case WhiteKingSideCastle:
10570       case WhiteQueenSideCastle:
10571       case BlackKingSideCastle:
10572       case BlackQueenSideCastle:
10573       case WhiteKingSideCastleWild:
10574       case WhiteQueenSideCastleWild:
10575       case BlackKingSideCastleWild:
10576       case BlackQueenSideCastleWild:
10577       /* PUSH Fabien */
10578       case WhiteHSideCastleFR:
10579       case WhiteASideCastleFR:
10580       case BlackHSideCastleFR:
10581       case BlackASideCastleFR:
10582       /* POP Fabien */
10583         if (appData.debugMode)
10584           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10585         fromX = currentMoveString[0] - AAA;
10586         fromY = currentMoveString[1] - ONE;
10587         toX = currentMoveString[2] - AAA;
10588         toY = currentMoveString[3] - ONE;
10589         promoChar = currentMoveString[4];
10590         break;
10591
10592       case WhiteDrop:
10593       case BlackDrop:
10594         if (appData.debugMode)
10595           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10596         fromX = moveType == WhiteDrop ?
10597           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10598         (int) CharToPiece(ToLower(currentMoveString[0]));
10599         fromY = DROP_RANK;
10600         toX = currentMoveString[2] - AAA;
10601         toY = currentMoveString[3] - ONE;
10602         break;
10603
10604       case WhiteWins:
10605       case BlackWins:
10606       case GameIsDrawn:
10607       case GameUnfinished:
10608         if (appData.debugMode)
10609           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10610         p = strchr(yy_text, '{');
10611         if (p == NULL) p = strchr(yy_text, '(');
10612         if (p == NULL) {
10613             p = yy_text;
10614             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10615         } else {
10616             q = strchr(p, *p == '{' ? '}' : ')');
10617             if (q != NULL) *q = NULLCHAR;
10618             p++;
10619         }
10620         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10621         GameEnds(moveType, p, GE_FILE);
10622         done = TRUE;
10623         if (cmailMsgLoaded) {
10624             ClearHighlights();
10625             flipView = WhiteOnMove(currentMove);
10626             if (moveType == GameUnfinished) flipView = !flipView;
10627             if (appData.debugMode)
10628               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10629         }
10630         break;
10631
10632       case EndOfFile:
10633         if (appData.debugMode)
10634           fprintf(debugFP, "Parser hit end of file\n");
10635         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10636           case MT_NONE:
10637           case MT_CHECK:
10638             break;
10639           case MT_CHECKMATE:
10640           case MT_STAINMATE:
10641             if (WhiteOnMove(currentMove)) {
10642                 GameEnds(BlackWins, "Black mates", GE_FILE);
10643             } else {
10644                 GameEnds(WhiteWins, "White mates", GE_FILE);
10645             }
10646             break;
10647           case MT_STALEMATE:
10648             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10649             break;
10650         }
10651         done = TRUE;
10652         break;
10653
10654       case MoveNumberOne:
10655         if (lastLoadGameStart == GNUChessGame) {
10656             /* GNUChessGames have numbers, but they aren't move numbers */
10657             if (appData.debugMode)
10658               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10659                       yy_text, (int) moveType);
10660             return LoadGameOneMove(EndOfFile); /* tail recursion */
10661         }
10662         /* else fall thru */
10663
10664       case XBoardGame:
10665       case GNUChessGame:
10666       case PGNTag:
10667         /* Reached start of next game in file */
10668         if (appData.debugMode)
10669           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10670         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10671           case MT_NONE:
10672           case MT_CHECK:
10673             break;
10674           case MT_CHECKMATE:
10675           case MT_STAINMATE:
10676             if (WhiteOnMove(currentMove)) {
10677                 GameEnds(BlackWins, "Black mates", GE_FILE);
10678             } else {
10679                 GameEnds(WhiteWins, "White mates", GE_FILE);
10680             }
10681             break;
10682           case MT_STALEMATE:
10683             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10684             break;
10685         }
10686         done = TRUE;
10687         break;
10688
10689       case PositionDiagram:     /* should not happen; ignore */
10690       case ElapsedTime:         /* ignore */
10691       case NAG:                 /* ignore */
10692         if (appData.debugMode)
10693           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10694                   yy_text, (int) moveType);
10695         return LoadGameOneMove(EndOfFile); /* tail recursion */
10696
10697       case IllegalMove:
10698         if (appData.testLegality) {
10699             if (appData.debugMode)
10700               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10701             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10702                     (forwardMostMove / 2) + 1,
10703                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10704             DisplayError(move, 0);
10705             done = TRUE;
10706         } else {
10707             if (appData.debugMode)
10708               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10709                       yy_text, currentMoveString);
10710             fromX = currentMoveString[0] - AAA;
10711             fromY = currentMoveString[1] - ONE;
10712             toX = currentMoveString[2] - AAA;
10713             toY = currentMoveString[3] - ONE;
10714             promoChar = currentMoveString[4];
10715         }
10716         break;
10717
10718       case AmbiguousMove:
10719         if (appData.debugMode)
10720           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10721         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10722                 (forwardMostMove / 2) + 1,
10723                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10724         DisplayError(move, 0);
10725         done = TRUE;
10726         break;
10727
10728       default:
10729       case ImpossibleMove:
10730         if (appData.debugMode)
10731           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10732         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10733                 (forwardMostMove / 2) + 1,
10734                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10735         DisplayError(move, 0);
10736         done = TRUE;
10737         break;
10738     }
10739
10740     if (done) {
10741         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10742             DrawPosition(FALSE, boards[currentMove]);
10743             DisplayBothClocks();
10744             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10745               DisplayComment(currentMove - 1, commentList[currentMove]);
10746         }
10747         (void) StopLoadGameTimer();
10748         gameFileFP = NULL;
10749         cmailOldMove = forwardMostMove;
10750         return FALSE;
10751     } else {
10752         /* currentMoveString is set as a side-effect of yylex */
10753
10754         thinkOutput[0] = NULLCHAR;
10755         MakeMove(fromX, fromY, toX, toY, promoChar);
10756         currentMove = forwardMostMove;
10757         return TRUE;
10758     }
10759 }
10760
10761 /* Load the nth game from the given file */
10762 int
10763 LoadGameFromFile(filename, n, title, useList)
10764      char *filename;
10765      int n;
10766      char *title;
10767      /*Boolean*/ int useList;
10768 {
10769     FILE *f;
10770     char buf[MSG_SIZ];
10771
10772     if (strcmp(filename, "-") == 0) {
10773         f = stdin;
10774         title = "stdin";
10775     } else {
10776         f = fopen(filename, "rb");
10777         if (f == NULL) {
10778           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10779             DisplayError(buf, errno);
10780             return FALSE;
10781         }
10782     }
10783     if (fseek(f, 0, 0) == -1) {
10784         /* f is not seekable; probably a pipe */
10785         useList = FALSE;
10786     }
10787     if (useList && n == 0) {
10788         int error = GameListBuild(f);
10789         if (error) {
10790             DisplayError(_("Cannot build game list"), error);
10791         } else if (!ListEmpty(&gameList) &&
10792                    ((ListGame *) gameList.tailPred)->number > 1) {
10793             GameListPopUp(f, title);
10794             return TRUE;
10795         }
10796         GameListDestroy();
10797         n = 1;
10798     }
10799     if (n == 0) n = 1;
10800     return LoadGame(f, n, title, FALSE);
10801 }
10802
10803
10804 void
10805 MakeRegisteredMove()
10806 {
10807     int fromX, fromY, toX, toY;
10808     char promoChar;
10809     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10810         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10811           case CMAIL_MOVE:
10812           case CMAIL_DRAW:
10813             if (appData.debugMode)
10814               fprintf(debugFP, "Restoring %s for game %d\n",
10815                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10816
10817             thinkOutput[0] = NULLCHAR;
10818             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10819             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10820             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10821             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10822             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10823             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10824             MakeMove(fromX, fromY, toX, toY, promoChar);
10825             ShowMove(fromX, fromY, toX, toY);
10826
10827             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10828               case MT_NONE:
10829               case MT_CHECK:
10830                 break;
10831
10832               case MT_CHECKMATE:
10833               case MT_STAINMATE:
10834                 if (WhiteOnMove(currentMove)) {
10835                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10836                 } else {
10837                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10838                 }
10839                 break;
10840
10841               case MT_STALEMATE:
10842                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10843                 break;
10844             }
10845
10846             break;
10847
10848           case CMAIL_RESIGN:
10849             if (WhiteOnMove(currentMove)) {
10850                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10851             } else {
10852                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10853             }
10854             break;
10855
10856           case CMAIL_ACCEPT:
10857             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10858             break;
10859
10860           default:
10861             break;
10862         }
10863     }
10864
10865     return;
10866 }
10867
10868 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10869 int
10870 CmailLoadGame(f, gameNumber, title, useList)
10871      FILE *f;
10872      int gameNumber;
10873      char *title;
10874      int useList;
10875 {
10876     int retVal;
10877
10878     if (gameNumber > nCmailGames) {
10879         DisplayError(_("No more games in this message"), 0);
10880         return FALSE;
10881     }
10882     if (f == lastLoadGameFP) {
10883         int offset = gameNumber - lastLoadGameNumber;
10884         if (offset == 0) {
10885             cmailMsg[0] = NULLCHAR;
10886             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10887                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10888                 nCmailMovesRegistered--;
10889             }
10890             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10891             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10892                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10893             }
10894         } else {
10895             if (! RegisterMove()) return FALSE;
10896         }
10897     }
10898
10899     retVal = LoadGame(f, gameNumber, title, useList);
10900
10901     /* Make move registered during previous look at this game, if any */
10902     MakeRegisteredMove();
10903
10904     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10905         commentList[currentMove]
10906           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10907         DisplayComment(currentMove - 1, commentList[currentMove]);
10908     }
10909
10910     return retVal;
10911 }
10912
10913 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10914 int
10915 ReloadGame(offset)
10916      int offset;
10917 {
10918     int gameNumber = lastLoadGameNumber + offset;
10919     if (lastLoadGameFP == NULL) {
10920         DisplayError(_("No game has been loaded yet"), 0);
10921         return FALSE;
10922     }
10923     if (gameNumber <= 0) {
10924         DisplayError(_("Can't back up any further"), 0);
10925         return FALSE;
10926     }
10927     if (cmailMsgLoaded) {
10928         return CmailLoadGame(lastLoadGameFP, gameNumber,
10929                              lastLoadGameTitle, lastLoadGameUseList);
10930     } else {
10931         return LoadGame(lastLoadGameFP, gameNumber,
10932                         lastLoadGameTitle, lastLoadGameUseList);
10933     }
10934 }
10935
10936
10937
10938 /* Load the nth game from open file f */
10939 int
10940 LoadGame(f, gameNumber, title, useList)
10941      FILE *f;
10942      int gameNumber;
10943      char *title;
10944      int useList;
10945 {
10946     ChessMove cm;
10947     char buf[MSG_SIZ];
10948     int gn = gameNumber;
10949     ListGame *lg = NULL;
10950     int numPGNTags = 0;
10951     int err;
10952     GameMode oldGameMode;
10953     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10954
10955     if (appData.debugMode)
10956         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10957
10958     if (gameMode == Training )
10959         SetTrainingModeOff();
10960
10961     oldGameMode = gameMode;
10962     if (gameMode != BeginningOfGame) {
10963       Reset(FALSE, TRUE);
10964     }
10965
10966     gameFileFP = f;
10967     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10968         fclose(lastLoadGameFP);
10969     }
10970
10971     if (useList) {
10972         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10973
10974         if (lg) {
10975             fseek(f, lg->offset, 0);
10976             GameListHighlight(gameNumber);
10977             gn = 1;
10978         }
10979         else {
10980             DisplayError(_("Game number out of range"), 0);
10981             return FALSE;
10982         }
10983     } else {
10984         GameListDestroy();
10985         if (fseek(f, 0, 0) == -1) {
10986             if (f == lastLoadGameFP ?
10987                 gameNumber == lastLoadGameNumber + 1 :
10988                 gameNumber == 1) {
10989                 gn = 1;
10990             } else {
10991                 DisplayError(_("Can't seek on game file"), 0);
10992                 return FALSE;
10993             }
10994         }
10995     }
10996     lastLoadGameFP = f;
10997     lastLoadGameNumber = gameNumber;
10998     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10999     lastLoadGameUseList = useList;
11000
11001     yynewfile(f);
11002
11003     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11004       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11005                 lg->gameInfo.black);
11006             DisplayTitle(buf);
11007     } else if (*title != NULLCHAR) {
11008         if (gameNumber > 1) {
11009           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11010             DisplayTitle(buf);
11011         } else {
11012             DisplayTitle(title);
11013         }
11014     }
11015
11016     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11017         gameMode = PlayFromGameFile;
11018         ModeHighlight();
11019     }
11020
11021     currentMove = forwardMostMove = backwardMostMove = 0;
11022     CopyBoard(boards[0], initialPosition);
11023     StopClocks();
11024
11025     /*
11026      * Skip the first gn-1 games in the file.
11027      * Also skip over anything that precedes an identifiable
11028      * start of game marker, to avoid being confused by
11029      * garbage at the start of the file.  Currently
11030      * recognized start of game markers are the move number "1",
11031      * the pattern "gnuchess .* game", the pattern
11032      * "^[#;%] [^ ]* game file", and a PGN tag block.
11033      * A game that starts with one of the latter two patterns
11034      * will also have a move number 1, possibly
11035      * following a position diagram.
11036      * 5-4-02: Let's try being more lenient and allowing a game to
11037      * start with an unnumbered move.  Does that break anything?
11038      */
11039     cm = lastLoadGameStart = EndOfFile;
11040     while (gn > 0) {
11041         yyboardindex = forwardMostMove;
11042         cm = (ChessMove) Myylex();
11043         switch (cm) {
11044           case EndOfFile:
11045             if (cmailMsgLoaded) {
11046                 nCmailGames = CMAIL_MAX_GAMES - gn;
11047             } else {
11048                 Reset(TRUE, TRUE);
11049                 DisplayError(_("Game not found in file"), 0);
11050             }
11051             return FALSE;
11052
11053           case GNUChessGame:
11054           case XBoardGame:
11055             gn--;
11056             lastLoadGameStart = cm;
11057             break;
11058
11059           case MoveNumberOne:
11060             switch (lastLoadGameStart) {
11061               case GNUChessGame:
11062               case XBoardGame:
11063               case PGNTag:
11064                 break;
11065               case MoveNumberOne:
11066               case EndOfFile:
11067                 gn--;           /* count this game */
11068                 lastLoadGameStart = cm;
11069                 break;
11070               default:
11071                 /* impossible */
11072                 break;
11073             }
11074             break;
11075
11076           case PGNTag:
11077             switch (lastLoadGameStart) {
11078               case GNUChessGame:
11079               case PGNTag:
11080               case MoveNumberOne:
11081               case EndOfFile:
11082                 gn--;           /* count this game */
11083                 lastLoadGameStart = cm;
11084                 break;
11085               case XBoardGame:
11086                 lastLoadGameStart = cm; /* game counted already */
11087                 break;
11088               default:
11089                 /* impossible */
11090                 break;
11091             }
11092             if (gn > 0) {
11093                 do {
11094                     yyboardindex = forwardMostMove;
11095                     cm = (ChessMove) Myylex();
11096                 } while (cm == PGNTag || cm == Comment);
11097             }
11098             break;
11099
11100           case WhiteWins:
11101           case BlackWins:
11102           case GameIsDrawn:
11103             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11104                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11105                     != CMAIL_OLD_RESULT) {
11106                     nCmailResults ++ ;
11107                     cmailResult[  CMAIL_MAX_GAMES
11108                                 - gn - 1] = CMAIL_OLD_RESULT;
11109                 }
11110             }
11111             break;
11112
11113           case NormalMove:
11114             /* Only a NormalMove can be at the start of a game
11115              * without a position diagram. */
11116             if (lastLoadGameStart == EndOfFile ) {
11117               gn--;
11118               lastLoadGameStart = MoveNumberOne;
11119             }
11120             break;
11121
11122           default:
11123             break;
11124         }
11125     }
11126
11127     if (appData.debugMode)
11128       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11129
11130     if (cm == XBoardGame) {
11131         /* Skip any header junk before position diagram and/or move 1 */
11132         for (;;) {
11133             yyboardindex = forwardMostMove;
11134             cm = (ChessMove) Myylex();
11135
11136             if (cm == EndOfFile ||
11137                 cm == GNUChessGame || cm == XBoardGame) {
11138                 /* Empty game; pretend end-of-file and handle later */
11139                 cm = EndOfFile;
11140                 break;
11141             }
11142
11143             if (cm == MoveNumberOne || cm == PositionDiagram ||
11144                 cm == PGNTag || cm == Comment)
11145               break;
11146         }
11147     } else if (cm == GNUChessGame) {
11148         if (gameInfo.event != NULL) {
11149             free(gameInfo.event);
11150         }
11151         gameInfo.event = StrSave(yy_text);
11152     }
11153
11154     startedFromSetupPosition = FALSE;
11155     while (cm == PGNTag) {
11156         if (appData.debugMode)
11157           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11158         err = ParsePGNTag(yy_text, &gameInfo);
11159         if (!err) numPGNTags++;
11160
11161         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11162         if(gameInfo.variant != oldVariant) {
11163             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11164             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11165             InitPosition(TRUE);
11166             oldVariant = gameInfo.variant;
11167             if (appData.debugMode)
11168               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11169         }
11170
11171
11172         if (gameInfo.fen != NULL) {
11173           Board initial_position;
11174           startedFromSetupPosition = TRUE;
11175           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11176             Reset(TRUE, TRUE);
11177             DisplayError(_("Bad FEN position in file"), 0);
11178             return FALSE;
11179           }
11180           CopyBoard(boards[0], initial_position);
11181           if (blackPlaysFirst) {
11182             currentMove = forwardMostMove = backwardMostMove = 1;
11183             CopyBoard(boards[1], initial_position);
11184             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11185             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11186             timeRemaining[0][1] = whiteTimeRemaining;
11187             timeRemaining[1][1] = blackTimeRemaining;
11188             if (commentList[0] != NULL) {
11189               commentList[1] = commentList[0];
11190               commentList[0] = NULL;
11191             }
11192           } else {
11193             currentMove = forwardMostMove = backwardMostMove = 0;
11194           }
11195           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11196           {   int i;
11197               initialRulePlies = FENrulePlies;
11198               for( i=0; i< nrCastlingRights; i++ )
11199                   initialRights[i] = initial_position[CASTLING][i];
11200           }
11201           yyboardindex = forwardMostMove;
11202           free(gameInfo.fen);
11203           gameInfo.fen = NULL;
11204         }
11205
11206         yyboardindex = forwardMostMove;
11207         cm = (ChessMove) Myylex();
11208
11209         /* Handle comments interspersed among the tags */
11210         while (cm == Comment) {
11211             char *p;
11212             if (appData.debugMode)
11213               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11214             p = yy_text;
11215             AppendComment(currentMove, p, FALSE);
11216             yyboardindex = forwardMostMove;
11217             cm = (ChessMove) Myylex();
11218         }
11219     }
11220
11221     /* don't rely on existence of Event tag since if game was
11222      * pasted from clipboard the Event tag may not exist
11223      */
11224     if (numPGNTags > 0){
11225         char *tags;
11226         if (gameInfo.variant == VariantNormal) {
11227           VariantClass v = StringToVariant(gameInfo.event);
11228           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11229           if(v < VariantShogi) gameInfo.variant = v;
11230         }
11231         if (!matchMode) {
11232           if( appData.autoDisplayTags ) {
11233             tags = PGNTags(&gameInfo);
11234             TagsPopUp(tags, CmailMsg());
11235             free(tags);
11236           }
11237         }
11238     } else {
11239         /* Make something up, but don't display it now */
11240         SetGameInfo();
11241         TagsPopDown();
11242     }
11243
11244     if (cm == PositionDiagram) {
11245         int i, j;
11246         char *p;
11247         Board initial_position;
11248
11249         if (appData.debugMode)
11250           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11251
11252         if (!startedFromSetupPosition) {
11253             p = yy_text;
11254             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11255               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11256                 switch (*p) {
11257                   case '{':
11258                   case '[':
11259                   case '-':
11260                   case ' ':
11261                   case '\t':
11262                   case '\n':
11263                   case '\r':
11264                     break;
11265                   default:
11266                     initial_position[i][j++] = CharToPiece(*p);
11267                     break;
11268                 }
11269             while (*p == ' ' || *p == '\t' ||
11270                    *p == '\n' || *p == '\r') p++;
11271
11272             if (strncmp(p, "black", strlen("black"))==0)
11273               blackPlaysFirst = TRUE;
11274             else
11275               blackPlaysFirst = FALSE;
11276             startedFromSetupPosition = TRUE;
11277
11278             CopyBoard(boards[0], initial_position);
11279             if (blackPlaysFirst) {
11280                 currentMove = forwardMostMove = backwardMostMove = 1;
11281                 CopyBoard(boards[1], initial_position);
11282                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11283                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11284                 timeRemaining[0][1] = whiteTimeRemaining;
11285                 timeRemaining[1][1] = blackTimeRemaining;
11286                 if (commentList[0] != NULL) {
11287                     commentList[1] = commentList[0];
11288                     commentList[0] = NULL;
11289                 }
11290             } else {
11291                 currentMove = forwardMostMove = backwardMostMove = 0;
11292             }
11293         }
11294         yyboardindex = forwardMostMove;
11295         cm = (ChessMove) Myylex();
11296     }
11297
11298     if (first.pr == NoProc) {
11299         StartChessProgram(&first);
11300     }
11301     InitChessProgram(&first, FALSE);
11302     SendToProgram("force\n", &first);
11303     if (startedFromSetupPosition) {
11304         SendBoard(&first, forwardMostMove);
11305     if (appData.debugMode) {
11306         fprintf(debugFP, "Load Game\n");
11307     }
11308         DisplayBothClocks();
11309     }
11310
11311     /* [HGM] server: flag to write setup moves in broadcast file as one */
11312     loadFlag = appData.suppressLoadMoves;
11313
11314     while (cm == Comment) {
11315         char *p;
11316         if (appData.debugMode)
11317           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11318         p = yy_text;
11319         AppendComment(currentMove, p, FALSE);
11320         yyboardindex = forwardMostMove;
11321         cm = (ChessMove) Myylex();
11322     }
11323
11324     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11325         cm == WhiteWins || cm == BlackWins ||
11326         cm == GameIsDrawn || cm == GameUnfinished) {
11327         DisplayMessage("", _("No moves in game"));
11328         if (cmailMsgLoaded) {
11329             if (appData.debugMode)
11330               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11331             ClearHighlights();
11332             flipView = FALSE;
11333         }
11334         DrawPosition(FALSE, boards[currentMove]);
11335         DisplayBothClocks();
11336         gameMode = EditGame;
11337         ModeHighlight();
11338         gameFileFP = NULL;
11339         cmailOldMove = 0;
11340         return TRUE;
11341     }
11342
11343     // [HGM] PV info: routine tests if comment empty
11344     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11345         DisplayComment(currentMove - 1, commentList[currentMove]);
11346     }
11347     if (!matchMode && appData.timeDelay != 0)
11348       DrawPosition(FALSE, boards[currentMove]);
11349
11350     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11351       programStats.ok_to_send = 1;
11352     }
11353
11354     /* if the first token after the PGN tags is a move
11355      * and not move number 1, retrieve it from the parser
11356      */
11357     if (cm != MoveNumberOne)
11358         LoadGameOneMove(cm);
11359
11360     /* load the remaining moves from the file */
11361     while (LoadGameOneMove(EndOfFile)) {
11362       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11363       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11364     }
11365
11366     /* rewind to the start of the game */
11367     currentMove = backwardMostMove;
11368
11369     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11370
11371     if (oldGameMode == AnalyzeFile ||
11372         oldGameMode == AnalyzeMode) {
11373       AnalyzeFileEvent();
11374     }
11375
11376     if (matchMode || appData.timeDelay == 0) {
11377       ToEndEvent();
11378       gameMode = EditGame;
11379       ModeHighlight();
11380     } else if (appData.timeDelay > 0) {
11381       AutoPlayGameLoop();
11382     }
11383
11384     if (appData.debugMode)
11385         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11386
11387     loadFlag = 0; /* [HGM] true game starts */
11388     return TRUE;
11389 }
11390
11391 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11392 int
11393 ReloadPosition(offset)
11394      int offset;
11395 {
11396     int positionNumber = lastLoadPositionNumber + offset;
11397     if (lastLoadPositionFP == NULL) {
11398         DisplayError(_("No position has been loaded yet"), 0);
11399         return FALSE;
11400     }
11401     if (positionNumber <= 0) {
11402         DisplayError(_("Can't back up any further"), 0);
11403         return FALSE;
11404     }
11405     return LoadPosition(lastLoadPositionFP, positionNumber,
11406                         lastLoadPositionTitle);
11407 }
11408
11409 /* Load the nth position from the given file */
11410 int
11411 LoadPositionFromFile(filename, n, title)
11412      char *filename;
11413      int n;
11414      char *title;
11415 {
11416     FILE *f;
11417     char buf[MSG_SIZ];
11418
11419     if (strcmp(filename, "-") == 0) {
11420         return LoadPosition(stdin, n, "stdin");
11421     } else {
11422         f = fopen(filename, "rb");
11423         if (f == NULL) {
11424             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11425             DisplayError(buf, errno);
11426             return FALSE;
11427         } else {
11428             return LoadPosition(f, n, title);
11429         }
11430     }
11431 }
11432
11433 /* Load the nth position from the given open file, and close it */
11434 int
11435 LoadPosition(f, positionNumber, title)
11436      FILE *f;
11437      int positionNumber;
11438      char *title;
11439 {
11440     char *p, line[MSG_SIZ];
11441     Board initial_position;
11442     int i, j, fenMode, pn;
11443
11444     if (gameMode == Training )
11445         SetTrainingModeOff();
11446
11447     if (gameMode != BeginningOfGame) {
11448         Reset(FALSE, TRUE);
11449     }
11450     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11451         fclose(lastLoadPositionFP);
11452     }
11453     if (positionNumber == 0) positionNumber = 1;
11454     lastLoadPositionFP = f;
11455     lastLoadPositionNumber = positionNumber;
11456     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11457     if (first.pr == NoProc) {
11458       StartChessProgram(&first);
11459       InitChessProgram(&first, FALSE);
11460     }
11461     pn = positionNumber;
11462     if (positionNumber < 0) {
11463         /* Negative position number means to seek to that byte offset */
11464         if (fseek(f, -positionNumber, 0) == -1) {
11465             DisplayError(_("Can't seek on position file"), 0);
11466             return FALSE;
11467         };
11468         pn = 1;
11469     } else {
11470         if (fseek(f, 0, 0) == -1) {
11471             if (f == lastLoadPositionFP ?
11472                 positionNumber == lastLoadPositionNumber + 1 :
11473                 positionNumber == 1) {
11474                 pn = 1;
11475             } else {
11476                 DisplayError(_("Can't seek on position file"), 0);
11477                 return FALSE;
11478             }
11479         }
11480     }
11481     /* See if this file is FEN or old-style xboard */
11482     if (fgets(line, MSG_SIZ, f) == NULL) {
11483         DisplayError(_("Position not found in file"), 0);
11484         return FALSE;
11485     }
11486     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11487     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11488
11489     if (pn >= 2) {
11490         if (fenMode || line[0] == '#') pn--;
11491         while (pn > 0) {
11492             /* skip positions before number pn */
11493             if (fgets(line, MSG_SIZ, f) == NULL) {
11494                 Reset(TRUE, TRUE);
11495                 DisplayError(_("Position not found in file"), 0);
11496                 return FALSE;
11497             }
11498             if (fenMode || line[0] == '#') pn--;
11499         }
11500     }
11501
11502     if (fenMode) {
11503         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11504             DisplayError(_("Bad FEN position in file"), 0);
11505             return FALSE;
11506         }
11507     } else {
11508         (void) fgets(line, MSG_SIZ, f);
11509         (void) fgets(line, MSG_SIZ, f);
11510
11511         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11512             (void) fgets(line, MSG_SIZ, f);
11513             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11514                 if (*p == ' ')
11515                   continue;
11516                 initial_position[i][j++] = CharToPiece(*p);
11517             }
11518         }
11519
11520         blackPlaysFirst = FALSE;
11521         if (!feof(f)) {
11522             (void) fgets(line, MSG_SIZ, f);
11523             if (strncmp(line, "black", strlen("black"))==0)
11524               blackPlaysFirst = TRUE;
11525         }
11526     }
11527     startedFromSetupPosition = TRUE;
11528
11529     SendToProgram("force\n", &first);
11530     CopyBoard(boards[0], initial_position);
11531     if (blackPlaysFirst) {
11532         currentMove = forwardMostMove = backwardMostMove = 1;
11533         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11534         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11535         CopyBoard(boards[1], initial_position);
11536         DisplayMessage("", _("Black to play"));
11537     } else {
11538         currentMove = forwardMostMove = backwardMostMove = 0;
11539         DisplayMessage("", _("White to play"));
11540     }
11541     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11542     SendBoard(&first, forwardMostMove);
11543     if (appData.debugMode) {
11544 int i, j;
11545   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11546   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11547         fprintf(debugFP, "Load Position\n");
11548     }
11549
11550     if (positionNumber > 1) {
11551       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11552         DisplayTitle(line);
11553     } else {
11554         DisplayTitle(title);
11555     }
11556     gameMode = EditGame;
11557     ModeHighlight();
11558     ResetClocks();
11559     timeRemaining[0][1] = whiteTimeRemaining;
11560     timeRemaining[1][1] = blackTimeRemaining;
11561     DrawPosition(FALSE, boards[currentMove]);
11562
11563     return TRUE;
11564 }
11565
11566
11567 void
11568 CopyPlayerNameIntoFileName(dest, src)
11569      char **dest, *src;
11570 {
11571     while (*src != NULLCHAR && *src != ',') {
11572         if (*src == ' ') {
11573             *(*dest)++ = '_';
11574             src++;
11575         } else {
11576             *(*dest)++ = *src++;
11577         }
11578     }
11579 }
11580
11581 char *DefaultFileName(ext)
11582      char *ext;
11583 {
11584     static char def[MSG_SIZ];
11585     char *p;
11586
11587     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11588         p = def;
11589         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11590         *p++ = '-';
11591         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11592         *p++ = '.';
11593         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11594     } else {
11595         def[0] = NULLCHAR;
11596     }
11597     return def;
11598 }
11599
11600 /* Save the current game to the given file */
11601 int
11602 SaveGameToFile(filename, append)
11603      char *filename;
11604      int append;
11605 {
11606     FILE *f;
11607     char buf[MSG_SIZ];
11608     int result;
11609
11610     if (strcmp(filename, "-") == 0) {
11611         return SaveGame(stdout, 0, NULL);
11612     } else {
11613         f = fopen(filename, append ? "a" : "w");
11614         if (f == NULL) {
11615             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11616             DisplayError(buf, errno);
11617             return FALSE;
11618         } else {
11619             safeStrCpy(buf, lastMsg, MSG_SIZ);
11620             DisplayMessage(_("Waiting for access to save file"), "");
11621             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11622             DisplayMessage(_("Saving game"), "");
11623             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11624             result = SaveGame(f, 0, NULL);
11625             DisplayMessage(buf, "");
11626             return result;
11627         }
11628     }
11629 }
11630
11631 char *
11632 SavePart(str)
11633      char *str;
11634 {
11635     static char buf[MSG_SIZ];
11636     char *p;
11637
11638     p = strchr(str, ' ');
11639     if (p == NULL) return str;
11640     strncpy(buf, str, p - str);
11641     buf[p - str] = NULLCHAR;
11642     return buf;
11643 }
11644
11645 #define PGN_MAX_LINE 75
11646
11647 #define PGN_SIDE_WHITE  0
11648 #define PGN_SIDE_BLACK  1
11649
11650 /* [AS] */
11651 static int FindFirstMoveOutOfBook( int side )
11652 {
11653     int result = -1;
11654
11655     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11656         int index = backwardMostMove;
11657         int has_book_hit = 0;
11658
11659         if( (index % 2) != side ) {
11660             index++;
11661         }
11662
11663         while( index < forwardMostMove ) {
11664             /* Check to see if engine is in book */
11665             int depth = pvInfoList[index].depth;
11666             int score = pvInfoList[index].score;
11667             int in_book = 0;
11668
11669             if( depth <= 2 ) {
11670                 in_book = 1;
11671             }
11672             else if( score == 0 && depth == 63 ) {
11673                 in_book = 1; /* Zappa */
11674             }
11675             else if( score == 2 && depth == 99 ) {
11676                 in_book = 1; /* Abrok */
11677             }
11678
11679             has_book_hit += in_book;
11680
11681             if( ! in_book ) {
11682                 result = index;
11683
11684                 break;
11685             }
11686
11687             index += 2;
11688         }
11689     }
11690
11691     return result;
11692 }
11693
11694 /* [AS] */
11695 void GetOutOfBookInfo( char * buf )
11696 {
11697     int oob[2];
11698     int i;
11699     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11700
11701     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11702     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11703
11704     *buf = '\0';
11705
11706     if( oob[0] >= 0 || oob[1] >= 0 ) {
11707         for( i=0; i<2; i++ ) {
11708             int idx = oob[i];
11709
11710             if( idx >= 0 ) {
11711                 if( i > 0 && oob[0] >= 0 ) {
11712                     strcat( buf, "   " );
11713                 }
11714
11715                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11716                 sprintf( buf+strlen(buf), "%s%.2f",
11717                     pvInfoList[idx].score >= 0 ? "+" : "",
11718                     pvInfoList[idx].score / 100.0 );
11719             }
11720         }
11721     }
11722 }
11723
11724 /* Save game in PGN style and close the file */
11725 int
11726 SaveGamePGN(f)
11727      FILE *f;
11728 {
11729     int i, offset, linelen, newblock;
11730     time_t tm;
11731 //    char *movetext;
11732     char numtext[32];
11733     int movelen, numlen, blank;
11734     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11735
11736     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11737
11738     tm = time((time_t *) NULL);
11739
11740     PrintPGNTags(f, &gameInfo);
11741
11742     if (backwardMostMove > 0 || startedFromSetupPosition) {
11743         char *fen = PositionToFEN(backwardMostMove, NULL);
11744         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11745         fprintf(f, "\n{--------------\n");
11746         PrintPosition(f, backwardMostMove);
11747         fprintf(f, "--------------}\n");
11748         free(fen);
11749     }
11750     else {
11751         /* [AS] Out of book annotation */
11752         if( appData.saveOutOfBookInfo ) {
11753             char buf[64];
11754
11755             GetOutOfBookInfo( buf );
11756
11757             if( buf[0] != '\0' ) {
11758                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11759             }
11760         }
11761
11762         fprintf(f, "\n");
11763     }
11764
11765     i = backwardMostMove;
11766     linelen = 0;
11767     newblock = TRUE;
11768
11769     while (i < forwardMostMove) {
11770         /* Print comments preceding this move */
11771         if (commentList[i] != NULL) {
11772             if (linelen > 0) fprintf(f, "\n");
11773             fprintf(f, "%s", commentList[i]);
11774             linelen = 0;
11775             newblock = TRUE;
11776         }
11777
11778         /* Format move number */
11779         if ((i % 2) == 0)
11780           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11781         else
11782           if (newblock)
11783             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11784           else
11785             numtext[0] = NULLCHAR;
11786
11787         numlen = strlen(numtext);
11788         newblock = FALSE;
11789
11790         /* Print move number */
11791         blank = linelen > 0 && numlen > 0;
11792         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11793             fprintf(f, "\n");
11794             linelen = 0;
11795             blank = 0;
11796         }
11797         if (blank) {
11798             fprintf(f, " ");
11799             linelen++;
11800         }
11801         fprintf(f, "%s", numtext);
11802         linelen += numlen;
11803
11804         /* Get move */
11805         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11806         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11807
11808         /* Print move */
11809         blank = linelen > 0 && movelen > 0;
11810         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11811             fprintf(f, "\n");
11812             linelen = 0;
11813             blank = 0;
11814         }
11815         if (blank) {
11816             fprintf(f, " ");
11817             linelen++;
11818         }
11819         fprintf(f, "%s", move_buffer);
11820         linelen += movelen;
11821
11822         /* [AS] Add PV info if present */
11823         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11824             /* [HGM] add time */
11825             char buf[MSG_SIZ]; int seconds;
11826
11827             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11828
11829             if( seconds <= 0)
11830               buf[0] = 0;
11831             else
11832               if( seconds < 30 )
11833                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11834               else
11835                 {
11836                   seconds = (seconds + 4)/10; // round to full seconds
11837                   if( seconds < 60 )
11838                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11839                   else
11840                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11841                 }
11842
11843             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11844                       pvInfoList[i].score >= 0 ? "+" : "",
11845                       pvInfoList[i].score / 100.0,
11846                       pvInfoList[i].depth,
11847                       buf );
11848
11849             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11850
11851             /* Print score/depth */
11852             blank = linelen > 0 && movelen > 0;
11853             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11854                 fprintf(f, "\n");
11855                 linelen = 0;
11856                 blank = 0;
11857             }
11858             if (blank) {
11859                 fprintf(f, " ");
11860                 linelen++;
11861             }
11862             fprintf(f, "%s", move_buffer);
11863             linelen += movelen;
11864         }
11865
11866         i++;
11867     }
11868
11869     /* Start a new line */
11870     if (linelen > 0) fprintf(f, "\n");
11871
11872     /* Print comments after last move */
11873     if (commentList[i] != NULL) {
11874         fprintf(f, "%s\n", commentList[i]);
11875     }
11876
11877     /* Print result */
11878     if (gameInfo.resultDetails != NULL &&
11879         gameInfo.resultDetails[0] != NULLCHAR) {
11880         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11881                 PGNResult(gameInfo.result));
11882     } else {
11883         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11884     }
11885
11886     fclose(f);
11887     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11888     return TRUE;
11889 }
11890
11891 /* Save game in old style and close the file */
11892 int
11893 SaveGameOldStyle(f)
11894      FILE *f;
11895 {
11896     int i, offset;
11897     time_t tm;
11898
11899     tm = time((time_t *) NULL);
11900
11901     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11902     PrintOpponents(f);
11903
11904     if (backwardMostMove > 0 || startedFromSetupPosition) {
11905         fprintf(f, "\n[--------------\n");
11906         PrintPosition(f, backwardMostMove);
11907         fprintf(f, "--------------]\n");
11908     } else {
11909         fprintf(f, "\n");
11910     }
11911
11912     i = backwardMostMove;
11913     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11914
11915     while (i < forwardMostMove) {
11916         if (commentList[i] != NULL) {
11917             fprintf(f, "[%s]\n", commentList[i]);
11918         }
11919
11920         if ((i % 2) == 1) {
11921             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11922             i++;
11923         } else {
11924             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11925             i++;
11926             if (commentList[i] != NULL) {
11927                 fprintf(f, "\n");
11928                 continue;
11929             }
11930             if (i >= forwardMostMove) {
11931                 fprintf(f, "\n");
11932                 break;
11933             }
11934             fprintf(f, "%s\n", parseList[i]);
11935             i++;
11936         }
11937     }
11938
11939     if (commentList[i] != NULL) {
11940         fprintf(f, "[%s]\n", commentList[i]);
11941     }
11942
11943     /* This isn't really the old style, but it's close enough */
11944     if (gameInfo.resultDetails != NULL &&
11945         gameInfo.resultDetails[0] != NULLCHAR) {
11946         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11947                 gameInfo.resultDetails);
11948     } else {
11949         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11950     }
11951
11952     fclose(f);
11953     return TRUE;
11954 }
11955
11956 /* Save the current game to open file f and close the file */
11957 int
11958 SaveGame(f, dummy, dummy2)
11959      FILE *f;
11960      int dummy;
11961      char *dummy2;
11962 {
11963     if (gameMode == EditPosition) EditPositionDone(TRUE);
11964     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11965     if (appData.oldSaveStyle)
11966       return SaveGameOldStyle(f);
11967     else
11968       return SaveGamePGN(f);
11969 }
11970
11971 /* Save the current position to the given file */
11972 int
11973 SavePositionToFile(filename)
11974      char *filename;
11975 {
11976     FILE *f;
11977     char buf[MSG_SIZ];
11978
11979     if (strcmp(filename, "-") == 0) {
11980         return SavePosition(stdout, 0, NULL);
11981     } else {
11982         f = fopen(filename, "a");
11983         if (f == NULL) {
11984             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11985             DisplayError(buf, errno);
11986             return FALSE;
11987         } else {
11988             safeStrCpy(buf, lastMsg, MSG_SIZ);
11989             DisplayMessage(_("Waiting for access to save file"), "");
11990             flock(fileno(f), LOCK_EX); // [HGM] lock
11991             DisplayMessage(_("Saving position"), "");
11992             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11993             SavePosition(f, 0, NULL);
11994             DisplayMessage(buf, "");
11995             return TRUE;
11996         }
11997     }
11998 }
11999
12000 /* Save the current position to the given open file and close the file */
12001 int
12002 SavePosition(f, dummy, dummy2)
12003      FILE *f;
12004      int dummy;
12005      char *dummy2;
12006 {
12007     time_t tm;
12008     char *fen;
12009
12010     if (gameMode == EditPosition) EditPositionDone(TRUE);
12011     if (appData.oldSaveStyle) {
12012         tm = time((time_t *) NULL);
12013
12014         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12015         PrintOpponents(f);
12016         fprintf(f, "[--------------\n");
12017         PrintPosition(f, currentMove);
12018         fprintf(f, "--------------]\n");
12019     } else {
12020         fen = PositionToFEN(currentMove, NULL);
12021         fprintf(f, "%s\n", fen);
12022         free(fen);
12023     }
12024     fclose(f);
12025     return TRUE;
12026 }
12027
12028 void
12029 ReloadCmailMsgEvent(unregister)
12030      int unregister;
12031 {
12032 #if !WIN32
12033     static char *inFilename = NULL;
12034     static char *outFilename;
12035     int i;
12036     struct stat inbuf, outbuf;
12037     int status;
12038
12039     /* Any registered moves are unregistered if unregister is set, */
12040     /* i.e. invoked by the signal handler */
12041     if (unregister) {
12042         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12043             cmailMoveRegistered[i] = FALSE;
12044             if (cmailCommentList[i] != NULL) {
12045                 free(cmailCommentList[i]);
12046                 cmailCommentList[i] = NULL;
12047             }
12048         }
12049         nCmailMovesRegistered = 0;
12050     }
12051
12052     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12053         cmailResult[i] = CMAIL_NOT_RESULT;
12054     }
12055     nCmailResults = 0;
12056
12057     if (inFilename == NULL) {
12058         /* Because the filenames are static they only get malloced once  */
12059         /* and they never get freed                                      */
12060         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12061         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12062
12063         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12064         sprintf(outFilename, "%s.out", appData.cmailGameName);
12065     }
12066
12067     status = stat(outFilename, &outbuf);
12068     if (status < 0) {
12069         cmailMailedMove = FALSE;
12070     } else {
12071         status = stat(inFilename, &inbuf);
12072         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12073     }
12074
12075     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12076        counts the games, notes how each one terminated, etc.
12077
12078        It would be nice to remove this kludge and instead gather all
12079        the information while building the game list.  (And to keep it
12080        in the game list nodes instead of having a bunch of fixed-size
12081        parallel arrays.)  Note this will require getting each game's
12082        termination from the PGN tags, as the game list builder does
12083        not process the game moves.  --mann
12084        */
12085     cmailMsgLoaded = TRUE;
12086     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12087
12088     /* Load first game in the file or popup game menu */
12089     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12090
12091 #endif /* !WIN32 */
12092     return;
12093 }
12094
12095 int
12096 RegisterMove()
12097 {
12098     FILE *f;
12099     char string[MSG_SIZ];
12100
12101     if (   cmailMailedMove
12102         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12103         return TRUE;            /* Allow free viewing  */
12104     }
12105
12106     /* Unregister move to ensure that we don't leave RegisterMove        */
12107     /* with the move registered when the conditions for registering no   */
12108     /* longer hold                                                       */
12109     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12110         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12111         nCmailMovesRegistered --;
12112
12113         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12114           {
12115               free(cmailCommentList[lastLoadGameNumber - 1]);
12116               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12117           }
12118     }
12119
12120     if (cmailOldMove == -1) {
12121         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12122         return FALSE;
12123     }
12124
12125     if (currentMove > cmailOldMove + 1) {
12126         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12127         return FALSE;
12128     }
12129
12130     if (currentMove < cmailOldMove) {
12131         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12132         return FALSE;
12133     }
12134
12135     if (forwardMostMove > currentMove) {
12136         /* Silently truncate extra moves */
12137         TruncateGame();
12138     }
12139
12140     if (   (currentMove == cmailOldMove + 1)
12141         || (   (currentMove == cmailOldMove)
12142             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12143                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12144         if (gameInfo.result != GameUnfinished) {
12145             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12146         }
12147
12148         if (commentList[currentMove] != NULL) {
12149             cmailCommentList[lastLoadGameNumber - 1]
12150               = StrSave(commentList[currentMove]);
12151         }
12152         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12153
12154         if (appData.debugMode)
12155           fprintf(debugFP, "Saving %s for game %d\n",
12156                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12157
12158         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12159
12160         f = fopen(string, "w");
12161         if (appData.oldSaveStyle) {
12162             SaveGameOldStyle(f); /* also closes the file */
12163
12164             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12165             f = fopen(string, "w");
12166             SavePosition(f, 0, NULL); /* also closes the file */
12167         } else {
12168             fprintf(f, "{--------------\n");
12169             PrintPosition(f, currentMove);
12170             fprintf(f, "--------------}\n\n");
12171
12172             SaveGame(f, 0, NULL); /* also closes the file*/
12173         }
12174
12175         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12176         nCmailMovesRegistered ++;
12177     } else if (nCmailGames == 1) {
12178         DisplayError(_("You have not made a move yet"), 0);
12179         return FALSE;
12180     }
12181
12182     return TRUE;
12183 }
12184
12185 void
12186 MailMoveEvent()
12187 {
12188 #if !WIN32
12189     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12190     FILE *commandOutput;
12191     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12192     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12193     int nBuffers;
12194     int i;
12195     int archived;
12196     char *arcDir;
12197
12198     if (! cmailMsgLoaded) {
12199         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12200         return;
12201     }
12202
12203     if (nCmailGames == nCmailResults) {
12204         DisplayError(_("No unfinished games"), 0);
12205         return;
12206     }
12207
12208 #if CMAIL_PROHIBIT_REMAIL
12209     if (cmailMailedMove) {
12210       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);
12211         DisplayError(msg, 0);
12212         return;
12213     }
12214 #endif
12215
12216     if (! (cmailMailedMove || RegisterMove())) return;
12217
12218     if (   cmailMailedMove
12219         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12220       snprintf(string, MSG_SIZ, partCommandString,
12221                appData.debugMode ? " -v" : "", appData.cmailGameName);
12222         commandOutput = popen(string, "r");
12223
12224         if (commandOutput == NULL) {
12225             DisplayError(_("Failed to invoke cmail"), 0);
12226         } else {
12227             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12228                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12229             }
12230             if (nBuffers > 1) {
12231                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12232                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12233                 nBytes = MSG_SIZ - 1;
12234             } else {
12235                 (void) memcpy(msg, buffer, nBytes);
12236             }
12237             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12238
12239             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12240                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12241
12242                 archived = TRUE;
12243                 for (i = 0; i < nCmailGames; i ++) {
12244                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12245                         archived = FALSE;
12246                     }
12247                 }
12248                 if (   archived
12249                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12250                         != NULL)) {
12251                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12252                            arcDir,
12253                            appData.cmailGameName,
12254                            gameInfo.date);
12255                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12256                     cmailMsgLoaded = FALSE;
12257                 }
12258             }
12259
12260             DisplayInformation(msg);
12261             pclose(commandOutput);
12262         }
12263     } else {
12264         if ((*cmailMsg) != '\0') {
12265             DisplayInformation(cmailMsg);
12266         }
12267     }
12268
12269     return;
12270 #endif /* !WIN32 */
12271 }
12272
12273 char *
12274 CmailMsg()
12275 {
12276 #if WIN32
12277     return NULL;
12278 #else
12279     int  prependComma = 0;
12280     char number[5];
12281     char string[MSG_SIZ];       /* Space for game-list */
12282     int  i;
12283
12284     if (!cmailMsgLoaded) return "";
12285
12286     if (cmailMailedMove) {
12287       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12288     } else {
12289         /* Create a list of games left */
12290       snprintf(string, MSG_SIZ, "[");
12291         for (i = 0; i < nCmailGames; i ++) {
12292             if (! (   cmailMoveRegistered[i]
12293                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12294                 if (prependComma) {
12295                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12296                 } else {
12297                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12298                     prependComma = 1;
12299                 }
12300
12301                 strcat(string, number);
12302             }
12303         }
12304         strcat(string, "]");
12305
12306         if (nCmailMovesRegistered + nCmailResults == 0) {
12307             switch (nCmailGames) {
12308               case 1:
12309                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12310                 break;
12311
12312               case 2:
12313                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12314                 break;
12315
12316               default:
12317                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12318                          nCmailGames);
12319                 break;
12320             }
12321         } else {
12322             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12323               case 1:
12324                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12325                          string);
12326                 break;
12327
12328               case 0:
12329                 if (nCmailResults == nCmailGames) {
12330                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12331                 } else {
12332                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12333                 }
12334                 break;
12335
12336               default:
12337                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12338                          string);
12339             }
12340         }
12341     }
12342     return cmailMsg;
12343 #endif /* WIN32 */
12344 }
12345
12346 void
12347 ResetGameEvent()
12348 {
12349     if (gameMode == Training)
12350       SetTrainingModeOff();
12351
12352     Reset(TRUE, TRUE);
12353     cmailMsgLoaded = FALSE;
12354     if (appData.icsActive) {
12355       SendToICS(ics_prefix);
12356       SendToICS("refresh\n");
12357     }
12358 }
12359
12360 void
12361 ExitEvent(status)
12362      int status;
12363 {
12364     exiting++;
12365     if (exiting > 2) {
12366       /* Give up on clean exit */
12367       exit(status);
12368     }
12369     if (exiting > 1) {
12370       /* Keep trying for clean exit */
12371       return;
12372     }
12373
12374     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12375
12376     if (telnetISR != NULL) {
12377       RemoveInputSource(telnetISR);
12378     }
12379     if (icsPR != NoProc) {
12380       DestroyChildProcess(icsPR, TRUE);
12381     }
12382
12383     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12384     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12385
12386     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12387     /* make sure this other one finishes before killing it!                  */
12388     if(endingGame) { int count = 0;
12389         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12390         while(endingGame && count++ < 10) DoSleep(1);
12391         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12392     }
12393
12394     /* Kill off chess programs */
12395     if (first.pr != NoProc) {
12396         ExitAnalyzeMode();
12397
12398         DoSleep( appData.delayBeforeQuit );
12399         SendToProgram("quit\n", &first);
12400         DoSleep( appData.delayAfterQuit );
12401         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12402     }
12403     if (second.pr != NoProc) {
12404         DoSleep( appData.delayBeforeQuit );
12405         SendToProgram("quit\n", &second);
12406         DoSleep( appData.delayAfterQuit );
12407         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12408     }
12409     if (first.isr != NULL) {
12410         RemoveInputSource(first.isr);
12411     }
12412     if (second.isr != NULL) {
12413         RemoveInputSource(second.isr);
12414     }
12415
12416     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12417     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12418
12419     ShutDownFrontEnd();
12420     exit(status);
12421 }
12422
12423 void
12424 PauseEvent()
12425 {
12426     if (appData.debugMode)
12427         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12428     if (pausing) {
12429         pausing = FALSE;
12430         ModeHighlight();
12431         if (gameMode == MachinePlaysWhite ||
12432             gameMode == MachinePlaysBlack) {
12433             StartClocks();
12434         } else {
12435             DisplayBothClocks();
12436         }
12437         if (gameMode == PlayFromGameFile) {
12438             if (appData.timeDelay >= 0)
12439                 AutoPlayGameLoop();
12440         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12441             Reset(FALSE, TRUE);
12442             SendToICS(ics_prefix);
12443             SendToICS("refresh\n");
12444         } else if (currentMove < forwardMostMove) {
12445             ForwardInner(forwardMostMove);
12446         }
12447         pauseExamInvalid = FALSE;
12448     } else {
12449         switch (gameMode) {
12450           default:
12451             return;
12452           case IcsExamining:
12453             pauseExamForwardMostMove = forwardMostMove;
12454             pauseExamInvalid = FALSE;
12455             /* fall through */
12456           case IcsObserving:
12457           case IcsPlayingWhite:
12458           case IcsPlayingBlack:
12459             pausing = TRUE;
12460             ModeHighlight();
12461             return;
12462           case PlayFromGameFile:
12463             (void) StopLoadGameTimer();
12464             pausing = TRUE;
12465             ModeHighlight();
12466             break;
12467           case BeginningOfGame:
12468             if (appData.icsActive) return;
12469             /* else fall through */
12470           case MachinePlaysWhite:
12471           case MachinePlaysBlack:
12472           case TwoMachinesPlay:
12473             if (forwardMostMove == 0)
12474               return;           /* don't pause if no one has moved */
12475             if ((gameMode == MachinePlaysWhite &&
12476                  !WhiteOnMove(forwardMostMove)) ||
12477                 (gameMode == MachinePlaysBlack &&
12478                  WhiteOnMove(forwardMostMove))) {
12479                 StopClocks();
12480             }
12481             pausing = TRUE;
12482             ModeHighlight();
12483             break;
12484         }
12485     }
12486 }
12487
12488 void
12489 EditCommentEvent()
12490 {
12491     char title[MSG_SIZ];
12492
12493     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12494       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12495     } else {
12496       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12497                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12498                parseList[currentMove - 1]);
12499     }
12500
12501     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12502 }
12503
12504
12505 void
12506 EditTagsEvent()
12507 {
12508     char *tags = PGNTags(&gameInfo);
12509     bookUp = FALSE;
12510     EditTagsPopUp(tags, NULL);
12511     free(tags);
12512 }
12513
12514 void
12515 AnalyzeModeEvent()
12516 {
12517     if (appData.noChessProgram || gameMode == AnalyzeMode)
12518       return;
12519
12520     if (gameMode != AnalyzeFile) {
12521         if (!appData.icsEngineAnalyze) {
12522                EditGameEvent();
12523                if (gameMode != EditGame) return;
12524         }
12525         ResurrectChessProgram();
12526         SendToProgram("analyze\n", &first);
12527         first.analyzing = TRUE;
12528         /*first.maybeThinking = TRUE;*/
12529         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12530         EngineOutputPopUp();
12531     }
12532     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12533     pausing = FALSE;
12534     ModeHighlight();
12535     SetGameInfo();
12536
12537     StartAnalysisClock();
12538     GetTimeMark(&lastNodeCountTime);
12539     lastNodeCount = 0;
12540 }
12541
12542 void
12543 AnalyzeFileEvent()
12544 {
12545     if (appData.noChessProgram || gameMode == AnalyzeFile)
12546       return;
12547
12548     if (gameMode != AnalyzeMode) {
12549         EditGameEvent();
12550         if (gameMode != EditGame) return;
12551         ResurrectChessProgram();
12552         SendToProgram("analyze\n", &first);
12553         first.analyzing = TRUE;
12554         /*first.maybeThinking = TRUE;*/
12555         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12556         EngineOutputPopUp();
12557     }
12558     gameMode = AnalyzeFile;
12559     pausing = FALSE;
12560     ModeHighlight();
12561     SetGameInfo();
12562
12563     StartAnalysisClock();
12564     GetTimeMark(&lastNodeCountTime);
12565     lastNodeCount = 0;
12566 }
12567
12568 void
12569 MachineWhiteEvent()
12570 {
12571     char buf[MSG_SIZ];
12572     char *bookHit = NULL;
12573
12574     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12575       return;
12576
12577
12578     if (gameMode == PlayFromGameFile ||
12579         gameMode == TwoMachinesPlay  ||
12580         gameMode == Training         ||
12581         gameMode == AnalyzeMode      ||
12582         gameMode == EndOfGame)
12583         EditGameEvent();
12584
12585     if (gameMode == EditPosition)
12586         EditPositionDone(TRUE);
12587
12588     if (!WhiteOnMove(currentMove)) {
12589         DisplayError(_("It is not White's turn"), 0);
12590         return;
12591     }
12592
12593     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12594       ExitAnalyzeMode();
12595
12596     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12597         gameMode == AnalyzeFile)
12598         TruncateGame();
12599
12600     ResurrectChessProgram();    /* in case it isn't running */
12601     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12602         gameMode = MachinePlaysWhite;
12603         ResetClocks();
12604     } else
12605     gameMode = MachinePlaysWhite;
12606     pausing = FALSE;
12607     ModeHighlight();
12608     SetGameInfo();
12609     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12610     DisplayTitle(buf);
12611     if (first.sendName) {
12612       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12613       SendToProgram(buf, &first);
12614     }
12615     if (first.sendTime) {
12616       if (first.useColors) {
12617         SendToProgram("black\n", &first); /*gnu kludge*/
12618       }
12619       SendTimeRemaining(&first, TRUE);
12620     }
12621     if (first.useColors) {
12622       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12623     }
12624     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12625     SetMachineThinkingEnables();
12626     first.maybeThinking = TRUE;
12627     StartClocks();
12628     firstMove = FALSE;
12629
12630     if (appData.autoFlipView && !flipView) {
12631       flipView = !flipView;
12632       DrawPosition(FALSE, NULL);
12633       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12634     }
12635
12636     if(bookHit) { // [HGM] book: simulate book reply
12637         static char bookMove[MSG_SIZ]; // a bit generous?
12638
12639         programStats.nodes = programStats.depth = programStats.time =
12640         programStats.score = programStats.got_only_move = 0;
12641         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12642
12643         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12644         strcat(bookMove, bookHit);
12645         HandleMachineMove(bookMove, &first);
12646     }
12647 }
12648
12649 void
12650 MachineBlackEvent()
12651 {
12652   char buf[MSG_SIZ];
12653   char *bookHit = NULL;
12654
12655     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12656         return;
12657
12658
12659     if (gameMode == PlayFromGameFile ||
12660         gameMode == TwoMachinesPlay  ||
12661         gameMode == Training         ||
12662         gameMode == AnalyzeMode      ||
12663         gameMode == EndOfGame)
12664         EditGameEvent();
12665
12666     if (gameMode == EditPosition)
12667         EditPositionDone(TRUE);
12668
12669     if (WhiteOnMove(currentMove)) {
12670         DisplayError(_("It is not Black's turn"), 0);
12671         return;
12672     }
12673
12674     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12675       ExitAnalyzeMode();
12676
12677     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12678         gameMode == AnalyzeFile)
12679         TruncateGame();
12680
12681     ResurrectChessProgram();    /* in case it isn't running */
12682     gameMode = MachinePlaysBlack;
12683     pausing = FALSE;
12684     ModeHighlight();
12685     SetGameInfo();
12686     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12687     DisplayTitle(buf);
12688     if (first.sendName) {
12689       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12690       SendToProgram(buf, &first);
12691     }
12692     if (first.sendTime) {
12693       if (first.useColors) {
12694         SendToProgram("white\n", &first); /*gnu kludge*/
12695       }
12696       SendTimeRemaining(&first, FALSE);
12697     }
12698     if (first.useColors) {
12699       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12700     }
12701     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12702     SetMachineThinkingEnables();
12703     first.maybeThinking = TRUE;
12704     StartClocks();
12705
12706     if (appData.autoFlipView && flipView) {
12707       flipView = !flipView;
12708       DrawPosition(FALSE, NULL);
12709       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12710     }
12711     if(bookHit) { // [HGM] book: simulate book reply
12712         static char bookMove[MSG_SIZ]; // a bit generous?
12713
12714         programStats.nodes = programStats.depth = programStats.time =
12715         programStats.score = programStats.got_only_move = 0;
12716         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12717
12718         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12719         strcat(bookMove, bookHit);
12720         HandleMachineMove(bookMove, &first);
12721     }
12722 }
12723
12724
12725 void
12726 DisplayTwoMachinesTitle()
12727 {
12728     char buf[MSG_SIZ];
12729     if (appData.matchGames > 0) {
12730         if(appData.tourneyFile[0]) {
12731           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12732                    gameInfo.white, gameInfo.black,
12733                    nextGame+1, appData.matchGames+1,
12734                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12735         } else 
12736         if (first.twoMachinesColor[0] == 'w') {
12737           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12738                    gameInfo.white, gameInfo.black,
12739                    first.matchWins, second.matchWins,
12740                    matchGame - 1 - (first.matchWins + second.matchWins));
12741         } else {
12742           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12743                    gameInfo.white, gameInfo.black,
12744                    second.matchWins, first.matchWins,
12745                    matchGame - 1 - (first.matchWins + second.matchWins));
12746         }
12747     } else {
12748       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12749     }
12750     DisplayTitle(buf);
12751 }
12752
12753 void
12754 SettingsMenuIfReady()
12755 {
12756   if (second.lastPing != second.lastPong) {
12757     DisplayMessage("", _("Waiting for second chess program"));
12758     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12759     return;
12760   }
12761   ThawUI();
12762   DisplayMessage("", "");
12763   SettingsPopUp(&second);
12764 }
12765
12766 int
12767 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12768 {
12769     char buf[MSG_SIZ];
12770     if (cps->pr == NULL) {
12771         StartChessProgram(cps);
12772         if (cps->protocolVersion == 1) {
12773           retry();
12774         } else {
12775           /* kludge: allow timeout for initial "feature" command */
12776           FreezeUI();
12777           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12778           DisplayMessage("", buf);
12779           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12780         }
12781         return 1;
12782     }
12783     return 0;
12784 }
12785
12786 void
12787 TwoMachinesEvent P((void))
12788 {
12789     int i;
12790     char buf[MSG_SIZ];
12791     ChessProgramState *onmove;
12792     char *bookHit = NULL;
12793     static int stalling = 0;
12794     TimeMark now;
12795     long wait;
12796
12797     if (appData.noChessProgram) return;
12798
12799     switch (gameMode) {
12800       case TwoMachinesPlay:
12801         return;
12802       case MachinePlaysWhite:
12803       case MachinePlaysBlack:
12804         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12805             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12806             return;
12807         }
12808         /* fall through */
12809       case BeginningOfGame:
12810       case PlayFromGameFile:
12811       case EndOfGame:
12812         EditGameEvent();
12813         if (gameMode != EditGame) return;
12814         break;
12815       case EditPosition:
12816         EditPositionDone(TRUE);
12817         break;
12818       case AnalyzeMode:
12819       case AnalyzeFile:
12820         ExitAnalyzeMode();
12821         break;
12822       case EditGame:
12823       default:
12824         break;
12825     }
12826
12827 //    forwardMostMove = currentMove;
12828     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12829
12830     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12831
12832     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12833     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12834       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12835       return;
12836     }
12837     if(!stalling) {
12838       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12839       SendToProgram("force\n", &second);
12840       stalling = 1;
12841       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12842       return;
12843     }
12844     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12845     if(appData.matchPause>10000 || appData.matchPause<10)
12846                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12847     wait = SubtractTimeMarks(&now, &pauseStart);
12848     if(wait < appData.matchPause) {
12849         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12850         return;
12851     }
12852     stalling = 0;
12853     DisplayMessage("", "");
12854     if (startedFromSetupPosition) {
12855         SendBoard(&second, backwardMostMove);
12856     if (appData.debugMode) {
12857         fprintf(debugFP, "Two Machines\n");
12858     }
12859     }
12860     for (i = backwardMostMove; i < forwardMostMove; i++) {
12861         SendMoveToProgram(i, &second);
12862     }
12863
12864     gameMode = TwoMachinesPlay;
12865     pausing = FALSE;
12866     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12867     SetGameInfo();
12868     DisplayTwoMachinesTitle();
12869     firstMove = TRUE;
12870     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12871         onmove = &first;
12872     } else {
12873         onmove = &second;
12874     }
12875     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12876     SendToProgram(first.computerString, &first);
12877     if (first.sendName) {
12878       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12879       SendToProgram(buf, &first);
12880     }
12881     SendToProgram(second.computerString, &second);
12882     if (second.sendName) {
12883       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12884       SendToProgram(buf, &second);
12885     }
12886
12887     ResetClocks();
12888     if (!first.sendTime || !second.sendTime) {
12889         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12890         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12891     }
12892     if (onmove->sendTime) {
12893       if (onmove->useColors) {
12894         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12895       }
12896       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12897     }
12898     if (onmove->useColors) {
12899       SendToProgram(onmove->twoMachinesColor, onmove);
12900     }
12901     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12902 //    SendToProgram("go\n", onmove);
12903     onmove->maybeThinking = TRUE;
12904     SetMachineThinkingEnables();
12905
12906     StartClocks();
12907
12908     if(bookHit) { // [HGM] book: simulate book reply
12909         static char bookMove[MSG_SIZ]; // a bit generous?
12910
12911         programStats.nodes = programStats.depth = programStats.time =
12912         programStats.score = programStats.got_only_move = 0;
12913         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12914
12915         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12916         strcat(bookMove, bookHit);
12917         savedMessage = bookMove; // args for deferred call
12918         savedState = onmove;
12919         ScheduleDelayedEvent(DeferredBookMove, 1);
12920     }
12921 }
12922
12923 void
12924 TrainingEvent()
12925 {
12926     if (gameMode == Training) {
12927       SetTrainingModeOff();
12928       gameMode = PlayFromGameFile;
12929       DisplayMessage("", _("Training mode off"));
12930     } else {
12931       gameMode = Training;
12932       animateTraining = appData.animate;
12933
12934       /* make sure we are not already at the end of the game */
12935       if (currentMove < forwardMostMove) {
12936         SetTrainingModeOn();
12937         DisplayMessage("", _("Training mode on"));
12938       } else {
12939         gameMode = PlayFromGameFile;
12940         DisplayError(_("Already at end of game"), 0);
12941       }
12942     }
12943     ModeHighlight();
12944 }
12945
12946 void
12947 IcsClientEvent()
12948 {
12949     if (!appData.icsActive) return;
12950     switch (gameMode) {
12951       case IcsPlayingWhite:
12952       case IcsPlayingBlack:
12953       case IcsObserving:
12954       case IcsIdle:
12955       case BeginningOfGame:
12956       case IcsExamining:
12957         return;
12958
12959       case EditGame:
12960         break;
12961
12962       case EditPosition:
12963         EditPositionDone(TRUE);
12964         break;
12965
12966       case AnalyzeMode:
12967       case AnalyzeFile:
12968         ExitAnalyzeMode();
12969         break;
12970
12971       default:
12972         EditGameEvent();
12973         break;
12974     }
12975
12976     gameMode = IcsIdle;
12977     ModeHighlight();
12978     return;
12979 }
12980
12981
12982 void
12983 EditGameEvent()
12984 {
12985     int i;
12986
12987     switch (gameMode) {
12988       case Training:
12989         SetTrainingModeOff();
12990         break;
12991       case MachinePlaysWhite:
12992       case MachinePlaysBlack:
12993       case BeginningOfGame:
12994         SendToProgram("force\n", &first);
12995         SetUserThinkingEnables();
12996         break;
12997       case PlayFromGameFile:
12998         (void) StopLoadGameTimer();
12999         if (gameFileFP != NULL) {
13000             gameFileFP = NULL;
13001         }
13002         break;
13003       case EditPosition:
13004         EditPositionDone(TRUE);
13005         break;
13006       case AnalyzeMode:
13007       case AnalyzeFile:
13008         ExitAnalyzeMode();
13009         SendToProgram("force\n", &first);
13010         break;
13011       case TwoMachinesPlay:
13012         GameEnds(EndOfFile, NULL, GE_PLAYER);
13013         ResurrectChessProgram();
13014         SetUserThinkingEnables();
13015         break;
13016       case EndOfGame:
13017         ResurrectChessProgram();
13018         break;
13019       case IcsPlayingBlack:
13020       case IcsPlayingWhite:
13021         DisplayError(_("Warning: You are still playing a game"), 0);
13022         break;
13023       case IcsObserving:
13024         DisplayError(_("Warning: You are still observing a game"), 0);
13025         break;
13026       case IcsExamining:
13027         DisplayError(_("Warning: You are still examining a game"), 0);
13028         break;
13029       case IcsIdle:
13030         break;
13031       case EditGame:
13032       default:
13033         return;
13034     }
13035
13036     pausing = FALSE;
13037     StopClocks();
13038     first.offeredDraw = second.offeredDraw = 0;
13039
13040     if (gameMode == PlayFromGameFile) {
13041         whiteTimeRemaining = timeRemaining[0][currentMove];
13042         blackTimeRemaining = timeRemaining[1][currentMove];
13043         DisplayTitle("");
13044     }
13045
13046     if (gameMode == MachinePlaysWhite ||
13047         gameMode == MachinePlaysBlack ||
13048         gameMode == TwoMachinesPlay ||
13049         gameMode == EndOfGame) {
13050         i = forwardMostMove;
13051         while (i > currentMove) {
13052             SendToProgram("undo\n", &first);
13053             i--;
13054         }
13055         whiteTimeRemaining = timeRemaining[0][currentMove];
13056         blackTimeRemaining = timeRemaining[1][currentMove];
13057         DisplayBothClocks();
13058         if (whiteFlag || blackFlag) {
13059             whiteFlag = blackFlag = 0;
13060         }
13061         DisplayTitle("");
13062     }
13063
13064     gameMode = EditGame;
13065     ModeHighlight();
13066     SetGameInfo();
13067 }
13068
13069
13070 void
13071 EditPositionEvent()
13072 {
13073     if (gameMode == EditPosition) {
13074         EditGameEvent();
13075         return;
13076     }
13077
13078     EditGameEvent();
13079     if (gameMode != EditGame) return;
13080
13081     gameMode = EditPosition;
13082     ModeHighlight();
13083     SetGameInfo();
13084     if (currentMove > 0)
13085       CopyBoard(boards[0], boards[currentMove]);
13086
13087     blackPlaysFirst = !WhiteOnMove(currentMove);
13088     ResetClocks();
13089     currentMove = forwardMostMove = backwardMostMove = 0;
13090     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13091     DisplayMove(-1);
13092 }
13093
13094 void
13095 ExitAnalyzeMode()
13096 {
13097     /* [DM] icsEngineAnalyze - possible call from other functions */
13098     if (appData.icsEngineAnalyze) {
13099         appData.icsEngineAnalyze = FALSE;
13100
13101         DisplayMessage("",_("Close ICS engine analyze..."));
13102     }
13103     if (first.analysisSupport && first.analyzing) {
13104       SendToProgram("exit\n", &first);
13105       first.analyzing = FALSE;
13106     }
13107     thinkOutput[0] = NULLCHAR;
13108 }
13109
13110 void
13111 EditPositionDone(Boolean fakeRights)
13112 {
13113     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13114
13115     startedFromSetupPosition = TRUE;
13116     InitChessProgram(&first, FALSE);
13117     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13118       boards[0][EP_STATUS] = EP_NONE;
13119       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13120     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13121         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13122         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13123       } else boards[0][CASTLING][2] = NoRights;
13124     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13125         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13126         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13127       } else boards[0][CASTLING][5] = NoRights;
13128     }
13129     SendToProgram("force\n", &first);
13130     if (blackPlaysFirst) {
13131         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13132         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13133         currentMove = forwardMostMove = backwardMostMove = 1;
13134         CopyBoard(boards[1], boards[0]);
13135     } else {
13136         currentMove = forwardMostMove = backwardMostMove = 0;
13137     }
13138     SendBoard(&first, forwardMostMove);
13139     if (appData.debugMode) {
13140         fprintf(debugFP, "EditPosDone\n");
13141     }
13142     DisplayTitle("");
13143     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13144     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13145     gameMode = EditGame;
13146     ModeHighlight();
13147     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13148     ClearHighlights(); /* [AS] */
13149 }
13150
13151 /* Pause for `ms' milliseconds */
13152 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13153 void
13154 TimeDelay(ms)
13155      long ms;
13156 {
13157     TimeMark m1, m2;
13158
13159     GetTimeMark(&m1);
13160     do {
13161         GetTimeMark(&m2);
13162     } while (SubtractTimeMarks(&m2, &m1) < ms);
13163 }
13164
13165 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13166 void
13167 SendMultiLineToICS(buf)
13168      char *buf;
13169 {
13170     char temp[MSG_SIZ+1], *p;
13171     int len;
13172
13173     len = strlen(buf);
13174     if (len > MSG_SIZ)
13175       len = MSG_SIZ;
13176
13177     strncpy(temp, buf, len);
13178     temp[len] = 0;
13179
13180     p = temp;
13181     while (*p) {
13182         if (*p == '\n' || *p == '\r')
13183           *p = ' ';
13184         ++p;
13185     }
13186
13187     strcat(temp, "\n");
13188     SendToICS(temp);
13189     SendToPlayer(temp, strlen(temp));
13190 }
13191
13192 void
13193 SetWhiteToPlayEvent()
13194 {
13195     if (gameMode == EditPosition) {
13196         blackPlaysFirst = FALSE;
13197         DisplayBothClocks();    /* works because currentMove is 0 */
13198     } else if (gameMode == IcsExamining) {
13199         SendToICS(ics_prefix);
13200         SendToICS("tomove white\n");
13201     }
13202 }
13203
13204 void
13205 SetBlackToPlayEvent()
13206 {
13207     if (gameMode == EditPosition) {
13208         blackPlaysFirst = TRUE;
13209         currentMove = 1;        /* kludge */
13210         DisplayBothClocks();
13211         currentMove = 0;
13212     } else if (gameMode == IcsExamining) {
13213         SendToICS(ics_prefix);
13214         SendToICS("tomove black\n");
13215     }
13216 }
13217
13218 void
13219 EditPositionMenuEvent(selection, x, y)
13220      ChessSquare selection;
13221      int x, y;
13222 {
13223     char buf[MSG_SIZ];
13224     ChessSquare piece = boards[0][y][x];
13225
13226     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13227
13228     switch (selection) {
13229       case ClearBoard:
13230         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13231             SendToICS(ics_prefix);
13232             SendToICS("bsetup clear\n");
13233         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13234             SendToICS(ics_prefix);
13235             SendToICS("clearboard\n");
13236         } else {
13237             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13238                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13239                 for (y = 0; y < BOARD_HEIGHT; y++) {
13240                     if (gameMode == IcsExamining) {
13241                         if (boards[currentMove][y][x] != EmptySquare) {
13242                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13243                                     AAA + x, ONE + y);
13244                             SendToICS(buf);
13245                         }
13246                     } else {
13247                         boards[0][y][x] = p;
13248                     }
13249                 }
13250             }
13251         }
13252         if (gameMode == EditPosition) {
13253             DrawPosition(FALSE, boards[0]);
13254         }
13255         break;
13256
13257       case WhitePlay:
13258         SetWhiteToPlayEvent();
13259         break;
13260
13261       case BlackPlay:
13262         SetBlackToPlayEvent();
13263         break;
13264
13265       case EmptySquare:
13266         if (gameMode == IcsExamining) {
13267             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13268             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13269             SendToICS(buf);
13270         } else {
13271             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13272                 if(x == BOARD_LEFT-2) {
13273                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13274                     boards[0][y][1] = 0;
13275                 } else
13276                 if(x == BOARD_RGHT+1) {
13277                     if(y >= gameInfo.holdingsSize) break;
13278                     boards[0][y][BOARD_WIDTH-2] = 0;
13279                 } else break;
13280             }
13281             boards[0][y][x] = EmptySquare;
13282             DrawPosition(FALSE, boards[0]);
13283         }
13284         break;
13285
13286       case PromotePiece:
13287         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13288            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13289             selection = (ChessSquare) (PROMOTED piece);
13290         } else if(piece == EmptySquare) selection = WhiteSilver;
13291         else selection = (ChessSquare)((int)piece - 1);
13292         goto defaultlabel;
13293
13294       case DemotePiece:
13295         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13296            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13297             selection = (ChessSquare) (DEMOTED piece);
13298         } else if(piece == EmptySquare) selection = BlackSilver;
13299         else selection = (ChessSquare)((int)piece + 1);
13300         goto defaultlabel;
13301
13302       case WhiteQueen:
13303       case BlackQueen:
13304         if(gameInfo.variant == VariantShatranj ||
13305            gameInfo.variant == VariantXiangqi  ||
13306            gameInfo.variant == VariantCourier  ||
13307            gameInfo.variant == VariantMakruk     )
13308             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13309         goto defaultlabel;
13310
13311       case WhiteKing:
13312       case BlackKing:
13313         if(gameInfo.variant == VariantXiangqi)
13314             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13315         if(gameInfo.variant == VariantKnightmate)
13316             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13317       default:
13318         defaultlabel:
13319         if (gameMode == IcsExamining) {
13320             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13321             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13322                      PieceToChar(selection), AAA + x, ONE + y);
13323             SendToICS(buf);
13324         } else {
13325             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13326                 int n;
13327                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13328                     n = PieceToNumber(selection - BlackPawn);
13329                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13330                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13331                     boards[0][BOARD_HEIGHT-1-n][1]++;
13332                 } else
13333                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13334                     n = PieceToNumber(selection);
13335                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13336                     boards[0][n][BOARD_WIDTH-1] = selection;
13337                     boards[0][n][BOARD_WIDTH-2]++;
13338                 }
13339             } else
13340             boards[0][y][x] = selection;
13341             DrawPosition(TRUE, boards[0]);
13342         }
13343         break;
13344     }
13345 }
13346
13347
13348 void
13349 DropMenuEvent(selection, x, y)
13350      ChessSquare selection;
13351      int x, y;
13352 {
13353     ChessMove moveType;
13354
13355     switch (gameMode) {
13356       case IcsPlayingWhite:
13357       case MachinePlaysBlack:
13358         if (!WhiteOnMove(currentMove)) {
13359             DisplayMoveError(_("It is Black's turn"));
13360             return;
13361         }
13362         moveType = WhiteDrop;
13363         break;
13364       case IcsPlayingBlack:
13365       case MachinePlaysWhite:
13366         if (WhiteOnMove(currentMove)) {
13367             DisplayMoveError(_("It is White's turn"));
13368             return;
13369         }
13370         moveType = BlackDrop;
13371         break;
13372       case EditGame:
13373         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13374         break;
13375       default:
13376         return;
13377     }
13378
13379     if (moveType == BlackDrop && selection < BlackPawn) {
13380       selection = (ChessSquare) ((int) selection
13381                                  + (int) BlackPawn - (int) WhitePawn);
13382     }
13383     if (boards[currentMove][y][x] != EmptySquare) {
13384         DisplayMoveError(_("That square is occupied"));
13385         return;
13386     }
13387
13388     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13389 }
13390
13391 void
13392 AcceptEvent()
13393 {
13394     /* Accept a pending offer of any kind from opponent */
13395
13396     if (appData.icsActive) {
13397         SendToICS(ics_prefix);
13398         SendToICS("accept\n");
13399     } else if (cmailMsgLoaded) {
13400         if (currentMove == cmailOldMove &&
13401             commentList[cmailOldMove] != NULL &&
13402             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13403                    "Black offers a draw" : "White offers a draw")) {
13404             TruncateGame();
13405             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13406             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13407         } else {
13408             DisplayError(_("There is no pending offer on this move"), 0);
13409             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13410         }
13411     } else {
13412         /* Not used for offers from chess program */
13413     }
13414 }
13415
13416 void
13417 DeclineEvent()
13418 {
13419     /* Decline a pending offer of any kind from opponent */
13420
13421     if (appData.icsActive) {
13422         SendToICS(ics_prefix);
13423         SendToICS("decline\n");
13424     } else if (cmailMsgLoaded) {
13425         if (currentMove == cmailOldMove &&
13426             commentList[cmailOldMove] != NULL &&
13427             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13428                    "Black offers a draw" : "White offers a draw")) {
13429 #ifdef NOTDEF
13430             AppendComment(cmailOldMove, "Draw declined", TRUE);
13431             DisplayComment(cmailOldMove - 1, "Draw declined");
13432 #endif /*NOTDEF*/
13433         } else {
13434             DisplayError(_("There is no pending offer on this move"), 0);
13435         }
13436     } else {
13437         /* Not used for offers from chess program */
13438     }
13439 }
13440
13441 void
13442 RematchEvent()
13443 {
13444     /* Issue ICS rematch command */
13445     if (appData.icsActive) {
13446         SendToICS(ics_prefix);
13447         SendToICS("rematch\n");
13448     }
13449 }
13450
13451 void
13452 CallFlagEvent()
13453 {
13454     /* Call your opponent's flag (claim a win on time) */
13455     if (appData.icsActive) {
13456         SendToICS(ics_prefix);
13457         SendToICS("flag\n");
13458     } else {
13459         switch (gameMode) {
13460           default:
13461             return;
13462           case MachinePlaysWhite:
13463             if (whiteFlag) {
13464                 if (blackFlag)
13465                   GameEnds(GameIsDrawn, "Both players ran out of time",
13466                            GE_PLAYER);
13467                 else
13468                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13469             } else {
13470                 DisplayError(_("Your opponent is not out of time"), 0);
13471             }
13472             break;
13473           case MachinePlaysBlack:
13474             if (blackFlag) {
13475                 if (whiteFlag)
13476                   GameEnds(GameIsDrawn, "Both players ran out of time",
13477                            GE_PLAYER);
13478                 else
13479                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13480             } else {
13481                 DisplayError(_("Your opponent is not out of time"), 0);
13482             }
13483             break;
13484         }
13485     }
13486 }
13487
13488 void
13489 ClockClick(int which)
13490 {       // [HGM] code moved to back-end from winboard.c
13491         if(which) { // black clock
13492           if (gameMode == EditPosition || gameMode == IcsExamining) {
13493             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13494             SetBlackToPlayEvent();
13495           } else if (gameMode == EditGame || shiftKey) {
13496             AdjustClock(which, -1);
13497           } else if (gameMode == IcsPlayingWhite ||
13498                      gameMode == MachinePlaysBlack) {
13499             CallFlagEvent();
13500           }
13501         } else { // white clock
13502           if (gameMode == EditPosition || gameMode == IcsExamining) {
13503             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13504             SetWhiteToPlayEvent();
13505           } else if (gameMode == EditGame || shiftKey) {
13506             AdjustClock(which, -1);
13507           } else if (gameMode == IcsPlayingBlack ||
13508                    gameMode == MachinePlaysWhite) {
13509             CallFlagEvent();
13510           }
13511         }
13512 }
13513
13514 void
13515 DrawEvent()
13516 {
13517     /* Offer draw or accept pending draw offer from opponent */
13518
13519     if (appData.icsActive) {
13520         /* Note: tournament rules require draw offers to be
13521            made after you make your move but before you punch
13522            your clock.  Currently ICS doesn't let you do that;
13523            instead, you immediately punch your clock after making
13524            a move, but you can offer a draw at any time. */
13525
13526         SendToICS(ics_prefix);
13527         SendToICS("draw\n");
13528         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13529     } else if (cmailMsgLoaded) {
13530         if (currentMove == cmailOldMove &&
13531             commentList[cmailOldMove] != NULL &&
13532             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13533                    "Black offers a draw" : "White offers a draw")) {
13534             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13535             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13536         } else if (currentMove == cmailOldMove + 1) {
13537             char *offer = WhiteOnMove(cmailOldMove) ?
13538               "White offers a draw" : "Black offers a draw";
13539             AppendComment(currentMove, offer, TRUE);
13540             DisplayComment(currentMove - 1, offer);
13541             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13542         } else {
13543             DisplayError(_("You must make your move before offering a draw"), 0);
13544             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13545         }
13546     } else if (first.offeredDraw) {
13547         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13548     } else {
13549         if (first.sendDrawOffers) {
13550             SendToProgram("draw\n", &first);
13551             userOfferedDraw = TRUE;
13552         }
13553     }
13554 }
13555
13556 void
13557 AdjournEvent()
13558 {
13559     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13560
13561     if (appData.icsActive) {
13562         SendToICS(ics_prefix);
13563         SendToICS("adjourn\n");
13564     } else {
13565         /* Currently GNU Chess doesn't offer or accept Adjourns */
13566     }
13567 }
13568
13569
13570 void
13571 AbortEvent()
13572 {
13573     /* Offer Abort or accept pending Abort offer from opponent */
13574
13575     if (appData.icsActive) {
13576         SendToICS(ics_prefix);
13577         SendToICS("abort\n");
13578     } else {
13579         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13580     }
13581 }
13582
13583 void
13584 ResignEvent()
13585 {
13586     /* Resign.  You can do this even if it's not your turn. */
13587
13588     if (appData.icsActive) {
13589         SendToICS(ics_prefix);
13590         SendToICS("resign\n");
13591     } else {
13592         switch (gameMode) {
13593           case MachinePlaysWhite:
13594             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13595             break;
13596           case MachinePlaysBlack:
13597             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13598             break;
13599           case EditGame:
13600             if (cmailMsgLoaded) {
13601                 TruncateGame();
13602                 if (WhiteOnMove(cmailOldMove)) {
13603                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13604                 } else {
13605                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13606                 }
13607                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13608             }
13609             break;
13610           default:
13611             break;
13612         }
13613     }
13614 }
13615
13616
13617 void
13618 StopObservingEvent()
13619 {
13620     /* Stop observing current games */
13621     SendToICS(ics_prefix);
13622     SendToICS("unobserve\n");
13623 }
13624
13625 void
13626 StopExaminingEvent()
13627 {
13628     /* Stop observing current game */
13629     SendToICS(ics_prefix);
13630     SendToICS("unexamine\n");
13631 }
13632
13633 void
13634 ForwardInner(target)
13635      int target;
13636 {
13637     int limit;
13638
13639     if (appData.debugMode)
13640         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13641                 target, currentMove, forwardMostMove);
13642
13643     if (gameMode == EditPosition)
13644       return;
13645
13646     if (gameMode == PlayFromGameFile && !pausing)
13647       PauseEvent();
13648
13649     if (gameMode == IcsExamining && pausing)
13650       limit = pauseExamForwardMostMove;
13651     else
13652       limit = forwardMostMove;
13653
13654     if (target > limit) target = limit;
13655
13656     if (target > 0 && moveList[target - 1][0]) {
13657         int fromX, fromY, toX, toY;
13658         toX = moveList[target - 1][2] - AAA;
13659         toY = moveList[target - 1][3] - ONE;
13660         if (moveList[target - 1][1] == '@') {
13661             if (appData.highlightLastMove) {
13662                 SetHighlights(-1, -1, toX, toY);
13663             }
13664         } else {
13665             fromX = moveList[target - 1][0] - AAA;
13666             fromY = moveList[target - 1][1] - ONE;
13667             if (target == currentMove + 1) {
13668                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13669             }
13670             if (appData.highlightLastMove) {
13671                 SetHighlights(fromX, fromY, toX, toY);
13672             }
13673         }
13674     }
13675     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13676         gameMode == Training || gameMode == PlayFromGameFile ||
13677         gameMode == AnalyzeFile) {
13678         while (currentMove < target) {
13679             SendMoveToProgram(currentMove++, &first);
13680         }
13681     } else {
13682         currentMove = target;
13683     }
13684
13685     if (gameMode == EditGame || gameMode == EndOfGame) {
13686         whiteTimeRemaining = timeRemaining[0][currentMove];
13687         blackTimeRemaining = timeRemaining[1][currentMove];
13688     }
13689     DisplayBothClocks();
13690     DisplayMove(currentMove - 1);
13691     DrawPosition(FALSE, boards[currentMove]);
13692     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13693     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13694         DisplayComment(currentMove - 1, commentList[currentMove]);
13695     }
13696     DisplayBook(currentMove);
13697 }
13698
13699
13700 void
13701 ForwardEvent()
13702 {
13703     if (gameMode == IcsExamining && !pausing) {
13704         SendToICS(ics_prefix);
13705         SendToICS("forward\n");
13706     } else {
13707         ForwardInner(currentMove + 1);
13708     }
13709 }
13710
13711 void
13712 ToEndEvent()
13713 {
13714     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13715         /* to optimze, we temporarily turn off analysis mode while we feed
13716          * the remaining moves to the engine. Otherwise we get analysis output
13717          * after each move.
13718          */
13719         if (first.analysisSupport) {
13720           SendToProgram("exit\nforce\n", &first);
13721           first.analyzing = FALSE;
13722         }
13723     }
13724
13725     if (gameMode == IcsExamining && !pausing) {
13726         SendToICS(ics_prefix);
13727         SendToICS("forward 999999\n");
13728     } else {
13729         ForwardInner(forwardMostMove);
13730     }
13731
13732     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13733         /* we have fed all the moves, so reactivate analysis mode */
13734         SendToProgram("analyze\n", &first);
13735         first.analyzing = TRUE;
13736         /*first.maybeThinking = TRUE;*/
13737         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13738     }
13739 }
13740
13741 void
13742 BackwardInner(target)
13743      int target;
13744 {
13745     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13746
13747     if (appData.debugMode)
13748         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13749                 target, currentMove, forwardMostMove);
13750
13751     if (gameMode == EditPosition) return;
13752     if (currentMove <= backwardMostMove) {
13753         ClearHighlights();
13754         DrawPosition(full_redraw, boards[currentMove]);
13755         return;
13756     }
13757     if (gameMode == PlayFromGameFile && !pausing)
13758       PauseEvent();
13759
13760     if (moveList[target][0]) {
13761         int fromX, fromY, toX, toY;
13762         toX = moveList[target][2] - AAA;
13763         toY = moveList[target][3] - ONE;
13764         if (moveList[target][1] == '@') {
13765             if (appData.highlightLastMove) {
13766                 SetHighlights(-1, -1, toX, toY);
13767             }
13768         } else {
13769             fromX = moveList[target][0] - AAA;
13770             fromY = moveList[target][1] - ONE;
13771             if (target == currentMove - 1) {
13772                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13773             }
13774             if (appData.highlightLastMove) {
13775                 SetHighlights(fromX, fromY, toX, toY);
13776             }
13777         }
13778     }
13779     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13780         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13781         while (currentMove > target) {
13782             SendToProgram("undo\n", &first);
13783             currentMove--;
13784         }
13785     } else {
13786         currentMove = target;
13787     }
13788
13789     if (gameMode == EditGame || gameMode == EndOfGame) {
13790         whiteTimeRemaining = timeRemaining[0][currentMove];
13791         blackTimeRemaining = timeRemaining[1][currentMove];
13792     }
13793     DisplayBothClocks();
13794     DisplayMove(currentMove - 1);
13795     DrawPosition(full_redraw, boards[currentMove]);
13796     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13797     // [HGM] PV info: routine tests if comment empty
13798     DisplayComment(currentMove - 1, commentList[currentMove]);
13799     DisplayBook(currentMove);
13800 }
13801
13802 void
13803 BackwardEvent()
13804 {
13805     if (gameMode == IcsExamining && !pausing) {
13806         SendToICS(ics_prefix);
13807         SendToICS("backward\n");
13808     } else {
13809         BackwardInner(currentMove - 1);
13810     }
13811 }
13812
13813 void
13814 ToStartEvent()
13815 {
13816     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13817         /* to optimize, we temporarily turn off analysis mode while we undo
13818          * all the moves. Otherwise we get analysis output after each undo.
13819          */
13820         if (first.analysisSupport) {
13821           SendToProgram("exit\nforce\n", &first);
13822           first.analyzing = FALSE;
13823         }
13824     }
13825
13826     if (gameMode == IcsExamining && !pausing) {
13827         SendToICS(ics_prefix);
13828         SendToICS("backward 999999\n");
13829     } else {
13830         BackwardInner(backwardMostMove);
13831     }
13832
13833     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13834         /* we have fed all the moves, so reactivate analysis mode */
13835         SendToProgram("analyze\n", &first);
13836         first.analyzing = TRUE;
13837         /*first.maybeThinking = TRUE;*/
13838         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13839     }
13840 }
13841
13842 void
13843 ToNrEvent(int to)
13844 {
13845   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13846   if (to >= forwardMostMove) to = forwardMostMove;
13847   if (to <= backwardMostMove) to = backwardMostMove;
13848   if (to < currentMove) {
13849     BackwardInner(to);
13850   } else {
13851     ForwardInner(to);
13852   }
13853 }
13854
13855 void
13856 RevertEvent(Boolean annotate)
13857 {
13858     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13859         return;
13860     }
13861     if (gameMode != IcsExamining) {
13862         DisplayError(_("You are not examining a game"), 0);
13863         return;
13864     }
13865     if (pausing) {
13866         DisplayError(_("You can't revert while pausing"), 0);
13867         return;
13868     }
13869     SendToICS(ics_prefix);
13870     SendToICS("revert\n");
13871 }
13872
13873 void
13874 RetractMoveEvent()
13875 {
13876     switch (gameMode) {
13877       case MachinePlaysWhite:
13878       case MachinePlaysBlack:
13879         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13880             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13881             return;
13882         }
13883         if (forwardMostMove < 2) return;
13884         currentMove = forwardMostMove = forwardMostMove - 2;
13885         whiteTimeRemaining = timeRemaining[0][currentMove];
13886         blackTimeRemaining = timeRemaining[1][currentMove];
13887         DisplayBothClocks();
13888         DisplayMove(currentMove - 1);
13889         ClearHighlights();/*!! could figure this out*/
13890         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13891         SendToProgram("remove\n", &first);
13892         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13893         break;
13894
13895       case BeginningOfGame:
13896       default:
13897         break;
13898
13899       case IcsPlayingWhite:
13900       case IcsPlayingBlack:
13901         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13902             SendToICS(ics_prefix);
13903             SendToICS("takeback 2\n");
13904         } else {
13905             SendToICS(ics_prefix);
13906             SendToICS("takeback 1\n");
13907         }
13908         break;
13909     }
13910 }
13911
13912 void
13913 MoveNowEvent()
13914 {
13915     ChessProgramState *cps;
13916
13917     switch (gameMode) {
13918       case MachinePlaysWhite:
13919         if (!WhiteOnMove(forwardMostMove)) {
13920             DisplayError(_("It is your turn"), 0);
13921             return;
13922         }
13923         cps = &first;
13924         break;
13925       case MachinePlaysBlack:
13926         if (WhiteOnMove(forwardMostMove)) {
13927             DisplayError(_("It is your turn"), 0);
13928             return;
13929         }
13930         cps = &first;
13931         break;
13932       case TwoMachinesPlay:
13933         if (WhiteOnMove(forwardMostMove) ==
13934             (first.twoMachinesColor[0] == 'w')) {
13935             cps = &first;
13936         } else {
13937             cps = &second;
13938         }
13939         break;
13940       case BeginningOfGame:
13941       default:
13942         return;
13943     }
13944     SendToProgram("?\n", cps);
13945 }
13946
13947 void
13948 TruncateGameEvent()
13949 {
13950     EditGameEvent();
13951     if (gameMode != EditGame) return;
13952     TruncateGame();
13953 }
13954
13955 void
13956 TruncateGame()
13957 {
13958     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13959     if (forwardMostMove > currentMove) {
13960         if (gameInfo.resultDetails != NULL) {
13961             free(gameInfo.resultDetails);
13962             gameInfo.resultDetails = NULL;
13963             gameInfo.result = GameUnfinished;
13964         }
13965         forwardMostMove = currentMove;
13966         HistorySet(parseList, backwardMostMove, forwardMostMove,
13967                    currentMove-1);
13968     }
13969 }
13970
13971 void
13972 HintEvent()
13973 {
13974     if (appData.noChessProgram) return;
13975     switch (gameMode) {
13976       case MachinePlaysWhite:
13977         if (WhiteOnMove(forwardMostMove)) {
13978             DisplayError(_("Wait until your turn"), 0);
13979             return;
13980         }
13981         break;
13982       case BeginningOfGame:
13983       case MachinePlaysBlack:
13984         if (!WhiteOnMove(forwardMostMove)) {
13985             DisplayError(_("Wait until your turn"), 0);
13986             return;
13987         }
13988         break;
13989       default:
13990         DisplayError(_("No hint available"), 0);
13991         return;
13992     }
13993     SendToProgram("hint\n", &first);
13994     hintRequested = TRUE;
13995 }
13996
13997 void
13998 BookEvent()
13999 {
14000     if (appData.noChessProgram) return;
14001     switch (gameMode) {
14002       case MachinePlaysWhite:
14003         if (WhiteOnMove(forwardMostMove)) {
14004             DisplayError(_("Wait until your turn"), 0);
14005             return;
14006         }
14007         break;
14008       case BeginningOfGame:
14009       case MachinePlaysBlack:
14010         if (!WhiteOnMove(forwardMostMove)) {
14011             DisplayError(_("Wait until your turn"), 0);
14012             return;
14013         }
14014         break;
14015       case EditPosition:
14016         EditPositionDone(TRUE);
14017         break;
14018       case TwoMachinesPlay:
14019         return;
14020       default:
14021         break;
14022     }
14023     SendToProgram("bk\n", &first);
14024     bookOutput[0] = NULLCHAR;
14025     bookRequested = TRUE;
14026 }
14027
14028 void
14029 AboutGameEvent()
14030 {
14031     char *tags = PGNTags(&gameInfo);
14032     TagsPopUp(tags, CmailMsg());
14033     free(tags);
14034 }
14035
14036 /* end button procedures */
14037
14038 void
14039 PrintPosition(fp, move)
14040      FILE *fp;
14041      int move;
14042 {
14043     int i, j;
14044
14045     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14046         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14047             char c = PieceToChar(boards[move][i][j]);
14048             fputc(c == 'x' ? '.' : c, fp);
14049             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14050         }
14051     }
14052     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14053       fprintf(fp, "white to play\n");
14054     else
14055       fprintf(fp, "black to play\n");
14056 }
14057
14058 void
14059 PrintOpponents(fp)
14060      FILE *fp;
14061 {
14062     if (gameInfo.white != NULL) {
14063         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14064     } else {
14065         fprintf(fp, "\n");
14066     }
14067 }
14068
14069 /* Find last component of program's own name, using some heuristics */
14070 void
14071 TidyProgramName(prog, host, buf)
14072      char *prog, *host, buf[MSG_SIZ];
14073 {
14074     char *p, *q;
14075     int local = (strcmp(host, "localhost") == 0);
14076     while (!local && (p = strchr(prog, ';')) != NULL) {
14077         p++;
14078         while (*p == ' ') p++;
14079         prog = p;
14080     }
14081     if (*prog == '"' || *prog == '\'') {
14082         q = strchr(prog + 1, *prog);
14083     } else {
14084         q = strchr(prog, ' ');
14085     }
14086     if (q == NULL) q = prog + strlen(prog);
14087     p = q;
14088     while (p >= prog && *p != '/' && *p != '\\') p--;
14089     p++;
14090     if(p == prog && *p == '"') p++;
14091     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14092     memcpy(buf, p, q - p);
14093     buf[q - p] = NULLCHAR;
14094     if (!local) {
14095         strcat(buf, "@");
14096         strcat(buf, host);
14097     }
14098 }
14099
14100 char *
14101 TimeControlTagValue()
14102 {
14103     char buf[MSG_SIZ];
14104     if (!appData.clockMode) {
14105       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14106     } else if (movesPerSession > 0) {
14107       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14108     } else if (timeIncrement == 0) {
14109       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14110     } else {
14111       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14112     }
14113     return StrSave(buf);
14114 }
14115
14116 void
14117 SetGameInfo()
14118 {
14119     /* This routine is used only for certain modes */
14120     VariantClass v = gameInfo.variant;
14121     ChessMove r = GameUnfinished;
14122     char *p = NULL;
14123
14124     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14125         r = gameInfo.result;
14126         p = gameInfo.resultDetails;
14127         gameInfo.resultDetails = NULL;
14128     }
14129     ClearGameInfo(&gameInfo);
14130     gameInfo.variant = v;
14131
14132     switch (gameMode) {
14133       case MachinePlaysWhite:
14134         gameInfo.event = StrSave( appData.pgnEventHeader );
14135         gameInfo.site = StrSave(HostName());
14136         gameInfo.date = PGNDate();
14137         gameInfo.round = StrSave("-");
14138         gameInfo.white = StrSave(first.tidy);
14139         gameInfo.black = StrSave(UserName());
14140         gameInfo.timeControl = TimeControlTagValue();
14141         break;
14142
14143       case MachinePlaysBlack:
14144         gameInfo.event = StrSave( appData.pgnEventHeader );
14145         gameInfo.site = StrSave(HostName());
14146         gameInfo.date = PGNDate();
14147         gameInfo.round = StrSave("-");
14148         gameInfo.white = StrSave(UserName());
14149         gameInfo.black = StrSave(first.tidy);
14150         gameInfo.timeControl = TimeControlTagValue();
14151         break;
14152
14153       case TwoMachinesPlay:
14154         gameInfo.event = StrSave( appData.pgnEventHeader );
14155         gameInfo.site = StrSave(HostName());
14156         gameInfo.date = PGNDate();
14157         if (roundNr > 0) {
14158             char buf[MSG_SIZ];
14159             snprintf(buf, MSG_SIZ, "%d", roundNr);
14160             gameInfo.round = StrSave(buf);
14161         } else {
14162             gameInfo.round = StrSave("-");
14163         }
14164         if (first.twoMachinesColor[0] == 'w') {
14165             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14166             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14167         } else {
14168             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14169             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14170         }
14171         gameInfo.timeControl = TimeControlTagValue();
14172         break;
14173
14174       case EditGame:
14175         gameInfo.event = StrSave("Edited game");
14176         gameInfo.site = StrSave(HostName());
14177         gameInfo.date = PGNDate();
14178         gameInfo.round = StrSave("-");
14179         gameInfo.white = StrSave("-");
14180         gameInfo.black = StrSave("-");
14181         gameInfo.result = r;
14182         gameInfo.resultDetails = p;
14183         break;
14184
14185       case EditPosition:
14186         gameInfo.event = StrSave("Edited position");
14187         gameInfo.site = StrSave(HostName());
14188         gameInfo.date = PGNDate();
14189         gameInfo.round = StrSave("-");
14190         gameInfo.white = StrSave("-");
14191         gameInfo.black = StrSave("-");
14192         break;
14193
14194       case IcsPlayingWhite:
14195       case IcsPlayingBlack:
14196       case IcsObserving:
14197       case IcsExamining:
14198         break;
14199
14200       case PlayFromGameFile:
14201         gameInfo.event = StrSave("Game from non-PGN file");
14202         gameInfo.site = StrSave(HostName());
14203         gameInfo.date = PGNDate();
14204         gameInfo.round = StrSave("-");
14205         gameInfo.white = StrSave("?");
14206         gameInfo.black = StrSave("?");
14207         break;
14208
14209       default:
14210         break;
14211     }
14212 }
14213
14214 void
14215 ReplaceComment(index, text)
14216      int index;
14217      char *text;
14218 {
14219     int len;
14220     char *p;
14221     float score;
14222
14223     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14224        pvInfoList[index-1].depth == len &&
14225        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14226        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14227     while (*text == '\n') text++;
14228     len = strlen(text);
14229     while (len > 0 && text[len - 1] == '\n') len--;
14230
14231     if (commentList[index] != NULL)
14232       free(commentList[index]);
14233
14234     if (len == 0) {
14235         commentList[index] = NULL;
14236         return;
14237     }
14238   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14239       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14240       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14241     commentList[index] = (char *) malloc(len + 2);
14242     strncpy(commentList[index], text, len);
14243     commentList[index][len] = '\n';
14244     commentList[index][len + 1] = NULLCHAR;
14245   } else {
14246     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14247     char *p;
14248     commentList[index] = (char *) malloc(len + 7);
14249     safeStrCpy(commentList[index], "{\n", 3);
14250     safeStrCpy(commentList[index]+2, text, len+1);
14251     commentList[index][len+2] = NULLCHAR;
14252     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14253     strcat(commentList[index], "\n}\n");
14254   }
14255 }
14256
14257 void
14258 CrushCRs(text)
14259      char *text;
14260 {
14261   char *p = text;
14262   char *q = text;
14263   char ch;
14264
14265   do {
14266     ch = *p++;
14267     if (ch == '\r') continue;
14268     *q++ = ch;
14269   } while (ch != '\0');
14270 }
14271
14272 void
14273 AppendComment(index, text, addBraces)
14274      int index;
14275      char *text;
14276      Boolean addBraces; // [HGM] braces: tells if we should add {}
14277 {
14278     int oldlen, len;
14279     char *old;
14280
14281 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14282     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14283
14284     CrushCRs(text);
14285     while (*text == '\n') text++;
14286     len = strlen(text);
14287     while (len > 0 && text[len - 1] == '\n') len--;
14288
14289     if (len == 0) return;
14290
14291     if (commentList[index] != NULL) {
14292         old = commentList[index];
14293         oldlen = strlen(old);
14294         while(commentList[index][oldlen-1] ==  '\n')
14295           commentList[index][--oldlen] = NULLCHAR;
14296         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14297         safeStrCpy(commentList[index], old, oldlen + len + 6);
14298         free(old);
14299         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14300         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14301           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14302           while (*text == '\n') { text++; len--; }
14303           commentList[index][--oldlen] = NULLCHAR;
14304       }
14305         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14306         else          strcat(commentList[index], "\n");
14307         strcat(commentList[index], text);
14308         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14309         else          strcat(commentList[index], "\n");
14310     } else {
14311         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14312         if(addBraces)
14313           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14314         else commentList[index][0] = NULLCHAR;
14315         strcat(commentList[index], text);
14316         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14317         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14318     }
14319 }
14320
14321 static char * FindStr( char * text, char * sub_text )
14322 {
14323     char * result = strstr( text, sub_text );
14324
14325     if( result != NULL ) {
14326         result += strlen( sub_text );
14327     }
14328
14329     return result;
14330 }
14331
14332 /* [AS] Try to extract PV info from PGN comment */
14333 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14334 char *GetInfoFromComment( int index, char * text )
14335 {
14336     char * sep = text, *p;
14337
14338     if( text != NULL && index > 0 ) {
14339         int score = 0;
14340         int depth = 0;
14341         int time = -1, sec = 0, deci;
14342         char * s_eval = FindStr( text, "[%eval " );
14343         char * s_emt = FindStr( text, "[%emt " );
14344
14345         if( s_eval != NULL || s_emt != NULL ) {
14346             /* New style */
14347             char delim;
14348
14349             if( s_eval != NULL ) {
14350                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14351                     return text;
14352                 }
14353
14354                 if( delim != ']' ) {
14355                     return text;
14356                 }
14357             }
14358
14359             if( s_emt != NULL ) {
14360             }
14361                 return text;
14362         }
14363         else {
14364             /* We expect something like: [+|-]nnn.nn/dd */
14365             int score_lo = 0;
14366
14367             if(*text != '{') return text; // [HGM] braces: must be normal comment
14368
14369             sep = strchr( text, '/' );
14370             if( sep == NULL || sep < (text+4) ) {
14371                 return text;
14372             }
14373
14374             p = text;
14375             if(p[1] == '(') { // comment starts with PV
14376                p = strchr(p, ')'); // locate end of PV
14377                if(p == NULL || sep < p+5) return text;
14378                // at this point we have something like "{(.*) +0.23/6 ..."
14379                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14380                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14381                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14382             }
14383             time = -1; sec = -1; deci = -1;
14384             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14385                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14386                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14387                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14388                 return text;
14389             }
14390
14391             if( score_lo < 0 || score_lo >= 100 ) {
14392                 return text;
14393             }
14394
14395             if(sec >= 0) time = 600*time + 10*sec; else
14396             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14397
14398             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14399
14400             /* [HGM] PV time: now locate end of PV info */
14401             while( *++sep >= '0' && *sep <= '9'); // strip depth
14402             if(time >= 0)
14403             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14404             if(sec >= 0)
14405             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14406             if(deci >= 0)
14407             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14408             while(*sep == ' ') sep++;
14409         }
14410
14411         if( depth <= 0 ) {
14412             return text;
14413         }
14414
14415         if( time < 0 ) {
14416             time = -1;
14417         }
14418
14419         pvInfoList[index-1].depth = depth;
14420         pvInfoList[index-1].score = score;
14421         pvInfoList[index-1].time  = 10*time; // centi-sec
14422         if(*sep == '}') *sep = 0; else *--sep = '{';
14423         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14424     }
14425     return sep;
14426 }
14427
14428 void
14429 SendToProgram(message, cps)
14430      char *message;
14431      ChessProgramState *cps;
14432 {
14433     int count, outCount, error;
14434     char buf[MSG_SIZ];
14435
14436     if (cps->pr == NULL) return;
14437     Attention(cps);
14438
14439     if (appData.debugMode) {
14440         TimeMark now;
14441         GetTimeMark(&now);
14442         fprintf(debugFP, "%ld >%-6s: %s",
14443                 SubtractTimeMarks(&now, &programStartTime),
14444                 cps->which, message);
14445     }
14446
14447     count = strlen(message);
14448     outCount = OutputToProcess(cps->pr, message, count, &error);
14449     if (outCount < count && !exiting
14450                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14451       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14452       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14453         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14454             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14455                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14456                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14457                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14458             } else {
14459                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14460                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14461                 gameInfo.result = res;
14462             }
14463             gameInfo.resultDetails = StrSave(buf);
14464         }
14465         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14466         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14467     }
14468 }
14469
14470 void
14471 ReceiveFromProgram(isr, closure, message, count, error)
14472      InputSourceRef isr;
14473      VOIDSTAR closure;
14474      char *message;
14475      int count;
14476      int error;
14477 {
14478     char *end_str;
14479     char buf[MSG_SIZ];
14480     ChessProgramState *cps = (ChessProgramState *)closure;
14481
14482     if (isr != cps->isr) return; /* Killed intentionally */
14483     if (count <= 0) {
14484         if (count == 0) {
14485             RemoveInputSource(cps->isr);
14486             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14487             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14488                     _(cps->which), cps->program);
14489         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14490                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14491                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14492                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14493                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14494                 } else {
14495                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14496                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14497                     gameInfo.result = res;
14498                 }
14499                 gameInfo.resultDetails = StrSave(buf);
14500             }
14501             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14502             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14503         } else {
14504             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14505                     _(cps->which), cps->program);
14506             RemoveInputSource(cps->isr);
14507
14508             /* [AS] Program is misbehaving badly... kill it */
14509             if( count == -2 ) {
14510                 DestroyChildProcess( cps->pr, 9 );
14511                 cps->pr = NoProc;
14512             }
14513
14514             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14515         }
14516         return;
14517     }
14518
14519     if ((end_str = strchr(message, '\r')) != NULL)
14520       *end_str = NULLCHAR;
14521     if ((end_str = strchr(message, '\n')) != NULL)
14522       *end_str = NULLCHAR;
14523
14524     if (appData.debugMode) {
14525         TimeMark now; int print = 1;
14526         char *quote = ""; char c; int i;
14527
14528         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14529                 char start = message[0];
14530                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14531                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14532                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14533                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14534                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14535                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14536                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14537                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14538                    sscanf(message, "hint: %c", &c)!=1 && 
14539                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14540                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14541                     print = (appData.engineComments >= 2);
14542                 }
14543                 message[0] = start; // restore original message
14544         }
14545         if(print) {
14546                 GetTimeMark(&now);
14547                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14548                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14549                         quote,
14550                         message);
14551         }
14552     }
14553
14554     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14555     if (appData.icsEngineAnalyze) {
14556         if (strstr(message, "whisper") != NULL ||
14557              strstr(message, "kibitz") != NULL ||
14558             strstr(message, "tellics") != NULL) return;
14559     }
14560
14561     HandleMachineMove(message, cps);
14562 }
14563
14564
14565 void
14566 SendTimeControl(cps, mps, tc, inc, sd, st)
14567      ChessProgramState *cps;
14568      int mps, inc, sd, st;
14569      long tc;
14570 {
14571     char buf[MSG_SIZ];
14572     int seconds;
14573
14574     if( timeControl_2 > 0 ) {
14575         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14576             tc = timeControl_2;
14577         }
14578     }
14579     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14580     inc /= cps->timeOdds;
14581     st  /= cps->timeOdds;
14582
14583     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14584
14585     if (st > 0) {
14586       /* Set exact time per move, normally using st command */
14587       if (cps->stKludge) {
14588         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14589         seconds = st % 60;
14590         if (seconds == 0) {
14591           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14592         } else {
14593           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14594         }
14595       } else {
14596         snprintf(buf, MSG_SIZ, "st %d\n", st);
14597       }
14598     } else {
14599       /* Set conventional or incremental time control, using level command */
14600       if (seconds == 0) {
14601         /* Note old gnuchess bug -- minutes:seconds used to not work.
14602            Fixed in later versions, but still avoid :seconds
14603            when seconds is 0. */
14604         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14605       } else {
14606         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14607                  seconds, inc/1000.);
14608       }
14609     }
14610     SendToProgram(buf, cps);
14611
14612     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14613     /* Orthogonally, limit search to given depth */
14614     if (sd > 0) {
14615       if (cps->sdKludge) {
14616         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14617       } else {
14618         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14619       }
14620       SendToProgram(buf, cps);
14621     }
14622
14623     if(cps->nps >= 0) { /* [HGM] nps */
14624         if(cps->supportsNPS == FALSE)
14625           cps->nps = -1; // don't use if engine explicitly says not supported!
14626         else {
14627           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14628           SendToProgram(buf, cps);
14629         }
14630     }
14631 }
14632
14633 ChessProgramState *WhitePlayer()
14634 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14635 {
14636     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14637        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14638         return &second;
14639     return &first;
14640 }
14641
14642 void
14643 SendTimeRemaining(cps, machineWhite)
14644      ChessProgramState *cps;
14645      int /*boolean*/ machineWhite;
14646 {
14647     char message[MSG_SIZ];
14648     long time, otime;
14649
14650     /* Note: this routine must be called when the clocks are stopped
14651        or when they have *just* been set or switched; otherwise
14652        it will be off by the time since the current tick started.
14653     */
14654     if (machineWhite) {
14655         time = whiteTimeRemaining / 10;
14656         otime = blackTimeRemaining / 10;
14657     } else {
14658         time = blackTimeRemaining / 10;
14659         otime = whiteTimeRemaining / 10;
14660     }
14661     /* [HGM] translate opponent's time by time-odds factor */
14662     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14663     if (appData.debugMode) {
14664         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14665     }
14666
14667     if (time <= 0) time = 1;
14668     if (otime <= 0) otime = 1;
14669
14670     snprintf(message, MSG_SIZ, "time %ld\n", time);
14671     SendToProgram(message, cps);
14672
14673     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14674     SendToProgram(message, cps);
14675 }
14676
14677 int
14678 BoolFeature(p, name, loc, cps)
14679      char **p;
14680      char *name;
14681      int *loc;
14682      ChessProgramState *cps;
14683 {
14684   char buf[MSG_SIZ];
14685   int len = strlen(name);
14686   int val;
14687
14688   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14689     (*p) += len + 1;
14690     sscanf(*p, "%d", &val);
14691     *loc = (val != 0);
14692     while (**p && **p != ' ')
14693       (*p)++;
14694     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14695     SendToProgram(buf, cps);
14696     return TRUE;
14697   }
14698   return FALSE;
14699 }
14700
14701 int
14702 IntFeature(p, name, loc, cps)
14703      char **p;
14704      char *name;
14705      int *loc;
14706      ChessProgramState *cps;
14707 {
14708   char buf[MSG_SIZ];
14709   int len = strlen(name);
14710   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14711     (*p) += len + 1;
14712     sscanf(*p, "%d", loc);
14713     while (**p && **p != ' ') (*p)++;
14714     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14715     SendToProgram(buf, cps);
14716     return TRUE;
14717   }
14718   return FALSE;
14719 }
14720
14721 int
14722 StringFeature(p, name, loc, cps)
14723      char **p;
14724      char *name;
14725      char loc[];
14726      ChessProgramState *cps;
14727 {
14728   char buf[MSG_SIZ];
14729   int len = strlen(name);
14730   if (strncmp((*p), name, len) == 0
14731       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14732     (*p) += len + 2;
14733     sscanf(*p, "%[^\"]", loc);
14734     while (**p && **p != '\"') (*p)++;
14735     if (**p == '\"') (*p)++;
14736     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14737     SendToProgram(buf, cps);
14738     return TRUE;
14739   }
14740   return FALSE;
14741 }
14742
14743 int
14744 ParseOption(Option *opt, ChessProgramState *cps)
14745 // [HGM] options: process the string that defines an engine option, and determine
14746 // name, type, default value, and allowed value range
14747 {
14748         char *p, *q, buf[MSG_SIZ];
14749         int n, min = (-1)<<31, max = 1<<31, def;
14750
14751         if(p = strstr(opt->name, " -spin ")) {
14752             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14753             if(max < min) max = min; // enforce consistency
14754             if(def < min) def = min;
14755             if(def > max) def = max;
14756             opt->value = def;
14757             opt->min = min;
14758             opt->max = max;
14759             opt->type = Spin;
14760         } else if((p = strstr(opt->name, " -slider "))) {
14761             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14762             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14763             if(max < min) max = min; // enforce consistency
14764             if(def < min) def = min;
14765             if(def > max) def = max;
14766             opt->value = def;
14767             opt->min = min;
14768             opt->max = max;
14769             opt->type = Spin; // Slider;
14770         } else if((p = strstr(opt->name, " -string "))) {
14771             opt->textValue = p+9;
14772             opt->type = TextBox;
14773         } else if((p = strstr(opt->name, " -file "))) {
14774             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14775             opt->textValue = p+7;
14776             opt->type = FileName; // FileName;
14777         } else if((p = strstr(opt->name, " -path "))) {
14778             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14779             opt->textValue = p+7;
14780             opt->type = PathName; // PathName;
14781         } else if(p = strstr(opt->name, " -check ")) {
14782             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14783             opt->value = (def != 0);
14784             opt->type = CheckBox;
14785         } else if(p = strstr(opt->name, " -combo ")) {
14786             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14787             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14788             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14789             opt->value = n = 0;
14790             while(q = StrStr(q, " /// ")) {
14791                 n++; *q = 0;    // count choices, and null-terminate each of them
14792                 q += 5;
14793                 if(*q == '*') { // remember default, which is marked with * prefix
14794                     q++;
14795                     opt->value = n;
14796                 }
14797                 cps->comboList[cps->comboCnt++] = q;
14798             }
14799             cps->comboList[cps->comboCnt++] = NULL;
14800             opt->max = n + 1;
14801             opt->type = ComboBox;
14802         } else if(p = strstr(opt->name, " -button")) {
14803             opt->type = Button;
14804         } else if(p = strstr(opt->name, " -save")) {
14805             opt->type = SaveButton;
14806         } else return FALSE;
14807         *p = 0; // terminate option name
14808         // now look if the command-line options define a setting for this engine option.
14809         if(cps->optionSettings && cps->optionSettings[0])
14810             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14811         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14812           snprintf(buf, MSG_SIZ, "option %s", p);
14813                 if(p = strstr(buf, ",")) *p = 0;
14814                 if(q = strchr(buf, '=')) switch(opt->type) {
14815                     case ComboBox:
14816                         for(n=0; n<opt->max; n++)
14817                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14818                         break;
14819                     case TextBox:
14820                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14821                         break;
14822                     case Spin:
14823                     case CheckBox:
14824                         opt->value = atoi(q+1);
14825                     default:
14826                         break;
14827                 }
14828                 strcat(buf, "\n");
14829                 SendToProgram(buf, cps);
14830         }
14831         return TRUE;
14832 }
14833
14834 void
14835 FeatureDone(cps, val)
14836      ChessProgramState* cps;
14837      int val;
14838 {
14839   DelayedEventCallback cb = GetDelayedEvent();
14840   if ((cb == InitBackEnd3 && cps == &first) ||
14841       (cb == SettingsMenuIfReady && cps == &second) ||
14842       (cb == LoadEngine) ||
14843       (cb == TwoMachinesEventIfReady)) {
14844     CancelDelayedEvent();
14845     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14846   }
14847   cps->initDone = val;
14848 }
14849
14850 /* Parse feature command from engine */
14851 void
14852 ParseFeatures(args, cps)
14853      char* args;
14854      ChessProgramState *cps;
14855 {
14856   char *p = args;
14857   char *q;
14858   int val;
14859   char buf[MSG_SIZ];
14860
14861   for (;;) {
14862     while (*p == ' ') p++;
14863     if (*p == NULLCHAR) return;
14864
14865     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14866     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14867     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14868     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14869     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14870     if (BoolFeature(&p, "reuse", &val, cps)) {
14871       /* Engine can disable reuse, but can't enable it if user said no */
14872       if (!val) cps->reuse = FALSE;
14873       continue;
14874     }
14875     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14876     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14877       if (gameMode == TwoMachinesPlay) {
14878         DisplayTwoMachinesTitle();
14879       } else {
14880         DisplayTitle("");
14881       }
14882       continue;
14883     }
14884     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14885     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14886     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14887     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14888     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14889     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14890     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14891     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14892     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14893     if (IntFeature(&p, "done", &val, cps)) {
14894       FeatureDone(cps, val);
14895       continue;
14896     }
14897     /* Added by Tord: */
14898     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14899     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14900     /* End of additions by Tord */
14901
14902     /* [HGM] added features: */
14903     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14904     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14905     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14906     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14907     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14908     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14909     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14910         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14911           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14912             SendToProgram(buf, cps);
14913             continue;
14914         }
14915         if(cps->nrOptions >= MAX_OPTIONS) {
14916             cps->nrOptions--;
14917             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14918             DisplayError(buf, 0);
14919         }
14920         continue;
14921     }
14922     /* End of additions by HGM */
14923
14924     /* unknown feature: complain and skip */
14925     q = p;
14926     while (*q && *q != '=') q++;
14927     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14928     SendToProgram(buf, cps);
14929     p = q;
14930     if (*p == '=') {
14931       p++;
14932       if (*p == '\"') {
14933         p++;
14934         while (*p && *p != '\"') p++;
14935         if (*p == '\"') p++;
14936       } else {
14937         while (*p && *p != ' ') p++;
14938       }
14939     }
14940   }
14941
14942 }
14943
14944 void
14945 PeriodicUpdatesEvent(newState)
14946      int newState;
14947 {
14948     if (newState == appData.periodicUpdates)
14949       return;
14950
14951     appData.periodicUpdates=newState;
14952
14953     /* Display type changes, so update it now */
14954 //    DisplayAnalysis();
14955
14956     /* Get the ball rolling again... */
14957     if (newState) {
14958         AnalysisPeriodicEvent(1);
14959         StartAnalysisClock();
14960     }
14961 }
14962
14963 void
14964 PonderNextMoveEvent(newState)
14965      int newState;
14966 {
14967     if (newState == appData.ponderNextMove) return;
14968     if (gameMode == EditPosition) EditPositionDone(TRUE);
14969     if (newState) {
14970         SendToProgram("hard\n", &first);
14971         if (gameMode == TwoMachinesPlay) {
14972             SendToProgram("hard\n", &second);
14973         }
14974     } else {
14975         SendToProgram("easy\n", &first);
14976         thinkOutput[0] = NULLCHAR;
14977         if (gameMode == TwoMachinesPlay) {
14978             SendToProgram("easy\n", &second);
14979         }
14980     }
14981     appData.ponderNextMove = newState;
14982 }
14983
14984 void
14985 NewSettingEvent(option, feature, command, value)
14986      char *command;
14987      int option, value, *feature;
14988 {
14989     char buf[MSG_SIZ];
14990
14991     if (gameMode == EditPosition) EditPositionDone(TRUE);
14992     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14993     if(feature == NULL || *feature) SendToProgram(buf, &first);
14994     if (gameMode == TwoMachinesPlay) {
14995         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14996     }
14997 }
14998
14999 void
15000 ShowThinkingEvent()
15001 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15002 {
15003     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15004     int newState = appData.showThinking
15005         // [HGM] thinking: other features now need thinking output as well
15006         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15007
15008     if (oldState == newState) return;
15009     oldState = newState;
15010     if (gameMode == EditPosition) EditPositionDone(TRUE);
15011     if (oldState) {
15012         SendToProgram("post\n", &first);
15013         if (gameMode == TwoMachinesPlay) {
15014             SendToProgram("post\n", &second);
15015         }
15016     } else {
15017         SendToProgram("nopost\n", &first);
15018         thinkOutput[0] = NULLCHAR;
15019         if (gameMode == TwoMachinesPlay) {
15020             SendToProgram("nopost\n", &second);
15021         }
15022     }
15023 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15024 }
15025
15026 void
15027 AskQuestionEvent(title, question, replyPrefix, which)
15028      char *title; char *question; char *replyPrefix; char *which;
15029 {
15030   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15031   if (pr == NoProc) return;
15032   AskQuestion(title, question, replyPrefix, pr);
15033 }
15034
15035 void
15036 TypeInEvent(char firstChar)
15037 {
15038     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15039         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15040         gameMode == AnalyzeMode || gameMode == EditGame || \r
15041         gameMode == EditPosition || gameMode == IcsExamining ||\r
15042         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15043         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15044                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15045                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15046         gameMode == Training) PopUpMoveDialog(firstChar);
15047 }
15048
15049 void
15050 TypeInDoneEvent(char *move)
15051 {
15052         Board board;
15053         int n, fromX, fromY, toX, toY;
15054         char promoChar;
15055         ChessMove moveType;\r
15056
15057         // [HGM] FENedit\r
15058         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15059                 EditPositionPasteFEN(move);\r
15060                 return;\r
15061         }\r
15062         // [HGM] movenum: allow move number to be typed in any mode\r
15063         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15064           ToNrEvent(2*n-1);\r
15065           return;\r
15066         }\r
15067
15068       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15069         gameMode != Training) {\r
15070         DisplayMoveError(_("Displayed move is not current"));\r
15071       } else {\r
15072         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15073           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15074         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15075         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15076           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15077           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15078         } else {\r
15079           DisplayMoveError(_("Could not parse move"));\r
15080         }
15081       }\r
15082 }\r
15083
15084 void
15085 DisplayMove(moveNumber)
15086      int moveNumber;
15087 {
15088     char message[MSG_SIZ];
15089     char res[MSG_SIZ];
15090     char cpThinkOutput[MSG_SIZ];
15091
15092     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15093
15094     if (moveNumber == forwardMostMove - 1 ||
15095         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15096
15097         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15098
15099         if (strchr(cpThinkOutput, '\n')) {
15100             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15101         }
15102     } else {
15103         *cpThinkOutput = NULLCHAR;
15104     }
15105
15106     /* [AS] Hide thinking from human user */
15107     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15108         *cpThinkOutput = NULLCHAR;
15109         if( thinkOutput[0] != NULLCHAR ) {
15110             int i;
15111
15112             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15113                 cpThinkOutput[i] = '.';
15114             }
15115             cpThinkOutput[i] = NULLCHAR;
15116             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15117         }
15118     }
15119
15120     if (moveNumber == forwardMostMove - 1 &&
15121         gameInfo.resultDetails != NULL) {
15122         if (gameInfo.resultDetails[0] == NULLCHAR) {
15123           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15124         } else {
15125           snprintf(res, MSG_SIZ, " {%s} %s",
15126                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15127         }
15128     } else {
15129         res[0] = NULLCHAR;
15130     }
15131
15132     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15133         DisplayMessage(res, cpThinkOutput);
15134     } else {
15135       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15136                 WhiteOnMove(moveNumber) ? " " : ".. ",
15137                 parseList[moveNumber], res);
15138         DisplayMessage(message, cpThinkOutput);
15139     }
15140 }
15141
15142 void
15143 DisplayComment(moveNumber, text)
15144      int moveNumber;
15145      char *text;
15146 {
15147     char title[MSG_SIZ];
15148     char buf[8000]; // comment can be long!
15149     int score, depth;
15150
15151     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15152       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15153     } else {
15154       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15155               WhiteOnMove(moveNumber) ? " " : ".. ",
15156               parseList[moveNumber]);
15157     }
15158     // [HGM] PV info: display PV info together with (or as) comment
15159     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15160       if(text == NULL) text = "";
15161       score = pvInfoList[moveNumber].score;
15162       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15163               depth, (pvInfoList[moveNumber].time+50)/100, text);
15164       text = buf;
15165     }
15166     if (text != NULL && (appData.autoDisplayComment || commentUp))
15167         CommentPopUp(title, text);
15168 }
15169
15170 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15171  * might be busy thinking or pondering.  It can be omitted if your
15172  * gnuchess is configured to stop thinking immediately on any user
15173  * input.  However, that gnuchess feature depends on the FIONREAD
15174  * ioctl, which does not work properly on some flavors of Unix.
15175  */
15176 void
15177 Attention(cps)
15178      ChessProgramState *cps;
15179 {
15180 #if ATTENTION
15181     if (!cps->useSigint) return;
15182     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15183     switch (gameMode) {
15184       case MachinePlaysWhite:
15185       case MachinePlaysBlack:
15186       case TwoMachinesPlay:
15187       case IcsPlayingWhite:
15188       case IcsPlayingBlack:
15189       case AnalyzeMode:
15190       case AnalyzeFile:
15191         /* Skip if we know it isn't thinking */
15192         if (!cps->maybeThinking) return;
15193         if (appData.debugMode)
15194           fprintf(debugFP, "Interrupting %s\n", cps->which);
15195         InterruptChildProcess(cps->pr);
15196         cps->maybeThinking = FALSE;
15197         break;
15198       default:
15199         break;
15200     }
15201 #endif /*ATTENTION*/
15202 }
15203
15204 int
15205 CheckFlags()
15206 {
15207     if (whiteTimeRemaining <= 0) {
15208         if (!whiteFlag) {
15209             whiteFlag = TRUE;
15210             if (appData.icsActive) {
15211                 if (appData.autoCallFlag &&
15212                     gameMode == IcsPlayingBlack && !blackFlag) {
15213                   SendToICS(ics_prefix);
15214                   SendToICS("flag\n");
15215                 }
15216             } else {
15217                 if (blackFlag) {
15218                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15219                 } else {
15220                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15221                     if (appData.autoCallFlag) {
15222                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15223                         return TRUE;
15224                     }
15225                 }
15226             }
15227         }
15228     }
15229     if (blackTimeRemaining <= 0) {
15230         if (!blackFlag) {
15231             blackFlag = TRUE;
15232             if (appData.icsActive) {
15233                 if (appData.autoCallFlag &&
15234                     gameMode == IcsPlayingWhite && !whiteFlag) {
15235                   SendToICS(ics_prefix);
15236                   SendToICS("flag\n");
15237                 }
15238             } else {
15239                 if (whiteFlag) {
15240                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15241                 } else {
15242                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15243                     if (appData.autoCallFlag) {
15244                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15245                         return TRUE;
15246                     }
15247                 }
15248             }
15249         }
15250     }
15251     return FALSE;
15252 }
15253
15254 void
15255 CheckTimeControl()
15256 {
15257     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15258         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15259
15260     /*
15261      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15262      */
15263     if ( !WhiteOnMove(forwardMostMove) ) {
15264         /* White made time control */
15265         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15266         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15267         /* [HGM] time odds: correct new time quota for time odds! */
15268                                             / WhitePlayer()->timeOdds;
15269         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15270     } else {
15271         lastBlack -= blackTimeRemaining;
15272         /* Black made time control */
15273         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15274                                             / WhitePlayer()->other->timeOdds;
15275         lastWhite = whiteTimeRemaining;
15276     }
15277 }
15278
15279 void
15280 DisplayBothClocks()
15281 {
15282     int wom = gameMode == EditPosition ?
15283       !blackPlaysFirst : WhiteOnMove(currentMove);
15284     DisplayWhiteClock(whiteTimeRemaining, wom);
15285     DisplayBlackClock(blackTimeRemaining, !wom);
15286 }
15287
15288
15289 /* Timekeeping seems to be a portability nightmare.  I think everyone
15290    has ftime(), but I'm really not sure, so I'm including some ifdefs
15291    to use other calls if you don't.  Clocks will be less accurate if
15292    you have neither ftime nor gettimeofday.
15293 */
15294
15295 /* VS 2008 requires the #include outside of the function */
15296 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15297 #include <sys/timeb.h>
15298 #endif
15299
15300 /* Get the current time as a TimeMark */
15301 void
15302 GetTimeMark(tm)
15303      TimeMark *tm;
15304 {
15305 #if HAVE_GETTIMEOFDAY
15306
15307     struct timeval timeVal;
15308     struct timezone timeZone;
15309
15310     gettimeofday(&timeVal, &timeZone);
15311     tm->sec = (long) timeVal.tv_sec;
15312     tm->ms = (int) (timeVal.tv_usec / 1000L);
15313
15314 #else /*!HAVE_GETTIMEOFDAY*/
15315 #if HAVE_FTIME
15316
15317 // include <sys/timeb.h> / moved to just above start of function
15318     struct timeb timeB;
15319
15320     ftime(&timeB);
15321     tm->sec = (long) timeB.time;
15322     tm->ms = (int) timeB.millitm;
15323
15324 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15325     tm->sec = (long) time(NULL);
15326     tm->ms = 0;
15327 #endif
15328 #endif
15329 }
15330
15331 /* Return the difference in milliseconds between two
15332    time marks.  We assume the difference will fit in a long!
15333 */
15334 long
15335 SubtractTimeMarks(tm2, tm1)
15336      TimeMark *tm2, *tm1;
15337 {
15338     return 1000L*(tm2->sec - tm1->sec) +
15339            (long) (tm2->ms - tm1->ms);
15340 }
15341
15342
15343 /*
15344  * Code to manage the game clocks.
15345  *
15346  * In tournament play, black starts the clock and then white makes a move.
15347  * We give the human user a slight advantage if he is playing white---the
15348  * clocks don't run until he makes his first move, so it takes zero time.
15349  * Also, we don't account for network lag, so we could get out of sync
15350  * with GNU Chess's clock -- but then, referees are always right.
15351  */
15352
15353 static TimeMark tickStartTM;
15354 static long intendedTickLength;
15355
15356 long
15357 NextTickLength(timeRemaining)
15358      long timeRemaining;
15359 {
15360     long nominalTickLength, nextTickLength;
15361
15362     if (timeRemaining > 0L && timeRemaining <= 10000L)
15363       nominalTickLength = 100L;
15364     else
15365       nominalTickLength = 1000L;
15366     nextTickLength = timeRemaining % nominalTickLength;
15367     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15368
15369     return nextTickLength;
15370 }
15371
15372 /* Adjust clock one minute up or down */
15373 void
15374 AdjustClock(Boolean which, int dir)
15375 {
15376     if(which) blackTimeRemaining += 60000*dir;
15377     else      whiteTimeRemaining += 60000*dir;
15378     DisplayBothClocks();
15379 }
15380
15381 /* Stop clocks and reset to a fresh time control */
15382 void
15383 ResetClocks()
15384 {
15385     (void) StopClockTimer();
15386     if (appData.icsActive) {
15387         whiteTimeRemaining = blackTimeRemaining = 0;
15388     } else if (searchTime) {
15389         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15390         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15391     } else { /* [HGM] correct new time quote for time odds */
15392         whiteTC = blackTC = fullTimeControlString;
15393         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15394         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15395     }
15396     if (whiteFlag || blackFlag) {
15397         DisplayTitle("");
15398         whiteFlag = blackFlag = FALSE;
15399     }
15400     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15401     DisplayBothClocks();
15402 }
15403
15404 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15405
15406 /* Decrement running clock by amount of time that has passed */
15407 void
15408 DecrementClocks()
15409 {
15410     long timeRemaining;
15411     long lastTickLength, fudge;
15412     TimeMark now;
15413
15414     if (!appData.clockMode) return;
15415     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15416
15417     GetTimeMark(&now);
15418
15419     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15420
15421     /* Fudge if we woke up a little too soon */
15422     fudge = intendedTickLength - lastTickLength;
15423     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15424
15425     if (WhiteOnMove(forwardMostMove)) {
15426         if(whiteNPS >= 0) lastTickLength = 0;
15427         timeRemaining = whiteTimeRemaining -= lastTickLength;
15428         if(timeRemaining < 0 && !appData.icsActive) {
15429             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15430             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15431                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15432                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15433             }
15434         }
15435         DisplayWhiteClock(whiteTimeRemaining - fudge,
15436                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15437     } else {
15438         if(blackNPS >= 0) lastTickLength = 0;
15439         timeRemaining = blackTimeRemaining -= lastTickLength;
15440         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15441             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15442             if(suddenDeath) {
15443                 blackStartMove = forwardMostMove;
15444                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15445             }
15446         }
15447         DisplayBlackClock(blackTimeRemaining - fudge,
15448                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15449     }
15450     if (CheckFlags()) return;
15451
15452     tickStartTM = now;
15453     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15454     StartClockTimer(intendedTickLength);
15455
15456     /* if the time remaining has fallen below the alarm threshold, sound the
15457      * alarm. if the alarm has sounded and (due to a takeback or time control
15458      * with increment) the time remaining has increased to a level above the
15459      * threshold, reset the alarm so it can sound again.
15460      */
15461
15462     if (appData.icsActive && appData.icsAlarm) {
15463
15464         /* make sure we are dealing with the user's clock */
15465         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15466                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15467            )) return;
15468
15469         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15470             alarmSounded = FALSE;
15471         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15472             PlayAlarmSound();
15473             alarmSounded = TRUE;
15474         }
15475     }
15476 }
15477
15478
15479 /* A player has just moved, so stop the previously running
15480    clock and (if in clock mode) start the other one.
15481    We redisplay both clocks in case we're in ICS mode, because
15482    ICS gives us an update to both clocks after every move.
15483    Note that this routine is called *after* forwardMostMove
15484    is updated, so the last fractional tick must be subtracted
15485    from the color that is *not* on move now.
15486 */
15487 void
15488 SwitchClocks(int newMoveNr)
15489 {
15490     long lastTickLength;
15491     TimeMark now;
15492     int flagged = FALSE;
15493
15494     GetTimeMark(&now);
15495
15496     if (StopClockTimer() && appData.clockMode) {
15497         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15498         if (!WhiteOnMove(forwardMostMove)) {
15499             if(blackNPS >= 0) lastTickLength = 0;
15500             blackTimeRemaining -= lastTickLength;
15501            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15502 //         if(pvInfoList[forwardMostMove].time == -1)
15503                  pvInfoList[forwardMostMove].time =               // use GUI time
15504                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15505         } else {
15506            if(whiteNPS >= 0) lastTickLength = 0;
15507            whiteTimeRemaining -= lastTickLength;
15508            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15509 //         if(pvInfoList[forwardMostMove].time == -1)
15510                  pvInfoList[forwardMostMove].time =
15511                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15512         }
15513         flagged = CheckFlags();
15514     }
15515     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15516     CheckTimeControl();
15517
15518     if (flagged || !appData.clockMode) return;
15519
15520     switch (gameMode) {
15521       case MachinePlaysBlack:
15522       case MachinePlaysWhite:
15523       case BeginningOfGame:
15524         if (pausing) return;
15525         break;
15526
15527       case EditGame:
15528       case PlayFromGameFile:
15529       case IcsExamining:
15530         return;
15531
15532       default:
15533         break;
15534     }
15535
15536     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15537         if(WhiteOnMove(forwardMostMove))
15538              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15539         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15540     }
15541
15542     tickStartTM = now;
15543     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15544       whiteTimeRemaining : blackTimeRemaining);
15545     StartClockTimer(intendedTickLength);
15546 }
15547
15548
15549 /* Stop both clocks */
15550 void
15551 StopClocks()
15552 {
15553     long lastTickLength;
15554     TimeMark now;
15555
15556     if (!StopClockTimer()) return;
15557     if (!appData.clockMode) return;
15558
15559     GetTimeMark(&now);
15560
15561     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15562     if (WhiteOnMove(forwardMostMove)) {
15563         if(whiteNPS >= 0) lastTickLength = 0;
15564         whiteTimeRemaining -= lastTickLength;
15565         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15566     } else {
15567         if(blackNPS >= 0) lastTickLength = 0;
15568         blackTimeRemaining -= lastTickLength;
15569         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15570     }
15571     CheckFlags();
15572 }
15573
15574 /* Start clock of player on move.  Time may have been reset, so
15575    if clock is already running, stop and restart it. */
15576 void
15577 StartClocks()
15578 {
15579     (void) StopClockTimer(); /* in case it was running already */
15580     DisplayBothClocks();
15581     if (CheckFlags()) return;
15582
15583     if (!appData.clockMode) return;
15584     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15585
15586     GetTimeMark(&tickStartTM);
15587     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15588       whiteTimeRemaining : blackTimeRemaining);
15589
15590    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15591     whiteNPS = blackNPS = -1;
15592     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15593        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15594         whiteNPS = first.nps;
15595     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15596        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15597         blackNPS = first.nps;
15598     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15599         whiteNPS = second.nps;
15600     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15601         blackNPS = second.nps;
15602     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15603
15604     StartClockTimer(intendedTickLength);
15605 }
15606
15607 char *
15608 TimeString(ms)
15609      long ms;
15610 {
15611     long second, minute, hour, day;
15612     char *sign = "";
15613     static char buf[32];
15614
15615     if (ms > 0 && ms <= 9900) {
15616       /* convert milliseconds to tenths, rounding up */
15617       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15618
15619       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15620       return buf;
15621     }
15622
15623     /* convert milliseconds to seconds, rounding up */
15624     /* use floating point to avoid strangeness of integer division
15625        with negative dividends on many machines */
15626     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15627
15628     if (second < 0) {
15629         sign = "-";
15630         second = -second;
15631     }
15632
15633     day = second / (60 * 60 * 24);
15634     second = second % (60 * 60 * 24);
15635     hour = second / (60 * 60);
15636     second = second % (60 * 60);
15637     minute = second / 60;
15638     second = second % 60;
15639
15640     if (day > 0)
15641       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15642               sign, day, hour, minute, second);
15643     else if (hour > 0)
15644       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15645     else
15646       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15647
15648     return buf;
15649 }
15650
15651
15652 /*
15653  * This is necessary because some C libraries aren't ANSI C compliant yet.
15654  */
15655 char *
15656 StrStr(string, match)
15657      char *string, *match;
15658 {
15659     int i, length;
15660
15661     length = strlen(match);
15662
15663     for (i = strlen(string) - length; i >= 0; i--, string++)
15664       if (!strncmp(match, string, length))
15665         return string;
15666
15667     return NULL;
15668 }
15669
15670 char *
15671 StrCaseStr(string, match)
15672      char *string, *match;
15673 {
15674     int i, j, length;
15675
15676     length = strlen(match);
15677
15678     for (i = strlen(string) - length; i >= 0; i--, string++) {
15679         for (j = 0; j < length; j++) {
15680             if (ToLower(match[j]) != ToLower(string[j]))
15681               break;
15682         }
15683         if (j == length) return string;
15684     }
15685
15686     return NULL;
15687 }
15688
15689 #ifndef _amigados
15690 int
15691 StrCaseCmp(s1, s2)
15692      char *s1, *s2;
15693 {
15694     char c1, c2;
15695
15696     for (;;) {
15697         c1 = ToLower(*s1++);
15698         c2 = ToLower(*s2++);
15699         if (c1 > c2) return 1;
15700         if (c1 < c2) return -1;
15701         if (c1 == NULLCHAR) return 0;
15702     }
15703 }
15704
15705
15706 int
15707 ToLower(c)
15708      int c;
15709 {
15710     return isupper(c) ? tolower(c) : c;
15711 }
15712
15713
15714 int
15715 ToUpper(c)
15716      int c;
15717 {
15718     return islower(c) ? toupper(c) : c;
15719 }
15720 #endif /* !_amigados    */
15721
15722 char *
15723 StrSave(s)
15724      char *s;
15725 {
15726   char *ret;
15727
15728   if ((ret = (char *) malloc(strlen(s) + 1)))
15729     {
15730       safeStrCpy(ret, s, strlen(s)+1);
15731     }
15732   return ret;
15733 }
15734
15735 char *
15736 StrSavePtr(s, savePtr)
15737      char *s, **savePtr;
15738 {
15739     if (*savePtr) {
15740         free(*savePtr);
15741     }
15742     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15743       safeStrCpy(*savePtr, s, strlen(s)+1);
15744     }
15745     return(*savePtr);
15746 }
15747
15748 char *
15749 PGNDate()
15750 {
15751     time_t clock;
15752     struct tm *tm;
15753     char buf[MSG_SIZ];
15754
15755     clock = time((time_t *)NULL);
15756     tm = localtime(&clock);
15757     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15758             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15759     return StrSave(buf);
15760 }
15761
15762
15763 char *
15764 PositionToFEN(move, overrideCastling)
15765      int move;
15766      char *overrideCastling;
15767 {
15768     int i, j, fromX, fromY, toX, toY;
15769     int whiteToPlay;
15770     char buf[128];
15771     char *p, *q;
15772     int emptycount;
15773     ChessSquare piece;
15774
15775     whiteToPlay = (gameMode == EditPosition) ?
15776       !blackPlaysFirst : (move % 2 == 0);
15777     p = buf;
15778
15779     /* Piece placement data */
15780     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15781         emptycount = 0;
15782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15783             if (boards[move][i][j] == EmptySquare) {
15784                 emptycount++;
15785             } else { ChessSquare piece = boards[move][i][j];
15786                 if (emptycount > 0) {
15787                     if(emptycount<10) /* [HGM] can be >= 10 */
15788                         *p++ = '0' + emptycount;
15789                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15790                     emptycount = 0;
15791                 }
15792                 if(PieceToChar(piece) == '+') {
15793                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15794                     *p++ = '+';
15795                     piece = (ChessSquare)(DEMOTED piece);
15796                 }
15797                 *p++ = PieceToChar(piece);
15798                 if(p[-1] == '~') {
15799                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15800                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15801                     *p++ = '~';
15802                 }
15803             }
15804         }
15805         if (emptycount > 0) {
15806             if(emptycount<10) /* [HGM] can be >= 10 */
15807                 *p++ = '0' + emptycount;
15808             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15809             emptycount = 0;
15810         }
15811         *p++ = '/';
15812     }
15813     *(p - 1) = ' ';
15814
15815     /* [HGM] print Crazyhouse or Shogi holdings */
15816     if( gameInfo.holdingsWidth ) {
15817         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15818         q = p;
15819         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15820             piece = boards[move][i][BOARD_WIDTH-1];
15821             if( piece != EmptySquare )
15822               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15823                   *p++ = PieceToChar(piece);
15824         }
15825         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15826             piece = boards[move][BOARD_HEIGHT-i-1][0];
15827             if( piece != EmptySquare )
15828               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15829                   *p++ = PieceToChar(piece);
15830         }
15831
15832         if( q == p ) *p++ = '-';
15833         *p++ = ']';
15834         *p++ = ' ';
15835     }
15836
15837     /* Active color */
15838     *p++ = whiteToPlay ? 'w' : 'b';
15839     *p++ = ' ';
15840
15841   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15842     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15843   } else {
15844   if(nrCastlingRights) {
15845      q = p;
15846      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15847        /* [HGM] write directly from rights */
15848            if(boards[move][CASTLING][2] != NoRights &&
15849               boards[move][CASTLING][0] != NoRights   )
15850                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15851            if(boards[move][CASTLING][2] != NoRights &&
15852               boards[move][CASTLING][1] != NoRights   )
15853                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15854            if(boards[move][CASTLING][5] != NoRights &&
15855               boards[move][CASTLING][3] != NoRights   )
15856                 *p++ = boards[move][CASTLING][3] + AAA;
15857            if(boards[move][CASTLING][5] != NoRights &&
15858               boards[move][CASTLING][4] != NoRights   )
15859                 *p++ = boards[move][CASTLING][4] + AAA;
15860      } else {
15861
15862         /* [HGM] write true castling rights */
15863         if( nrCastlingRights == 6 ) {
15864             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15865                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15866             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15867                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15868             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15869                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15870             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15871                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15872         }
15873      }
15874      if (q == p) *p++ = '-'; /* No castling rights */
15875      *p++ = ' ';
15876   }
15877
15878   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15879      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15880     /* En passant target square */
15881     if (move > backwardMostMove) {
15882         fromX = moveList[move - 1][0] - AAA;
15883         fromY = moveList[move - 1][1] - ONE;
15884         toX = moveList[move - 1][2] - AAA;
15885         toY = moveList[move - 1][3] - ONE;
15886         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15887             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15888             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15889             fromX == toX) {
15890             /* 2-square pawn move just happened */
15891             *p++ = toX + AAA;
15892             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15893         } else {
15894             *p++ = '-';
15895         }
15896     } else if(move == backwardMostMove) {
15897         // [HGM] perhaps we should always do it like this, and forget the above?
15898         if((signed char)boards[move][EP_STATUS] >= 0) {
15899             *p++ = boards[move][EP_STATUS] + AAA;
15900             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15901         } else {
15902             *p++ = '-';
15903         }
15904     } else {
15905         *p++ = '-';
15906     }
15907     *p++ = ' ';
15908   }
15909   }
15910
15911     /* [HGM] find reversible plies */
15912     {   int i = 0, j=move;
15913
15914         if (appData.debugMode) { int k;
15915             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15916             for(k=backwardMostMove; k<=forwardMostMove; k++)
15917                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15918
15919         }
15920
15921         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15922         if( j == backwardMostMove ) i += initialRulePlies;
15923         sprintf(p, "%d ", i);
15924         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15925     }
15926     /* Fullmove number */
15927     sprintf(p, "%d", (move / 2) + 1);
15928
15929     return StrSave(buf);
15930 }
15931
15932 Boolean
15933 ParseFEN(board, blackPlaysFirst, fen)
15934     Board board;
15935      int *blackPlaysFirst;
15936      char *fen;
15937 {
15938     int i, j;
15939     char *p, c;
15940     int emptycount;
15941     ChessSquare piece;
15942
15943     p = fen;
15944
15945     /* [HGM] by default clear Crazyhouse holdings, if present */
15946     if(gameInfo.holdingsWidth) {
15947        for(i=0; i<BOARD_HEIGHT; i++) {
15948            board[i][0]             = EmptySquare; /* black holdings */
15949            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15950            board[i][1]             = (ChessSquare) 0; /* black counts */
15951            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15952        }
15953     }
15954
15955     /* Piece placement data */
15956     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15957         j = 0;
15958         for (;;) {
15959             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15960                 if (*p == '/') p++;
15961                 emptycount = gameInfo.boardWidth - j;
15962                 while (emptycount--)
15963                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15964                 break;
15965 #if(BOARD_FILES >= 10)
15966             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15967                 p++; emptycount=10;
15968                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15969                 while (emptycount--)
15970                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15971 #endif
15972             } else if (isdigit(*p)) {
15973                 emptycount = *p++ - '0';
15974                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15975                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15976                 while (emptycount--)
15977                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15978             } else if (*p == '+' || isalpha(*p)) {
15979                 if (j >= gameInfo.boardWidth) return FALSE;
15980                 if(*p=='+') {
15981                     piece = CharToPiece(*++p);
15982                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15983                     piece = (ChessSquare) (PROMOTED piece ); p++;
15984                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15985                 } else piece = CharToPiece(*p++);
15986
15987                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15988                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15989                     piece = (ChessSquare) (PROMOTED piece);
15990                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15991                     p++;
15992                 }
15993                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15994             } else {
15995                 return FALSE;
15996             }
15997         }
15998     }
15999     while (*p == '/' || *p == ' ') p++;
16000
16001     /* [HGM] look for Crazyhouse holdings here */
16002     while(*p==' ') p++;
16003     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16004         if(*p == '[') p++;
16005         if(*p == '-' ) p++; /* empty holdings */ else {
16006             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16007             /* if we would allow FEN reading to set board size, we would   */
16008             /* have to add holdings and shift the board read so far here   */
16009             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16010                 p++;
16011                 if((int) piece >= (int) BlackPawn ) {
16012                     i = (int)piece - (int)BlackPawn;
16013                     i = PieceToNumber((ChessSquare)i);
16014                     if( i >= gameInfo.holdingsSize ) return FALSE;
16015                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16016                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16017                 } else {
16018                     i = (int)piece - (int)WhitePawn;
16019                     i = PieceToNumber((ChessSquare)i);
16020                     if( i >= gameInfo.holdingsSize ) return FALSE;
16021                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16022                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16023                 }
16024             }
16025         }
16026         if(*p == ']') p++;
16027     }
16028
16029     while(*p == ' ') p++;
16030
16031     /* Active color */
16032     c = *p++;
16033     if(appData.colorNickNames) {
16034       if( c == appData.colorNickNames[0] ) c = 'w'; else
16035       if( c == appData.colorNickNames[1] ) c = 'b';
16036     }
16037     switch (c) {
16038       case 'w':
16039         *blackPlaysFirst = FALSE;
16040         break;
16041       case 'b':
16042         *blackPlaysFirst = TRUE;
16043         break;
16044       default:
16045         return FALSE;
16046     }
16047
16048     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16049     /* return the extra info in global variiables             */
16050
16051     /* set defaults in case FEN is incomplete */
16052     board[EP_STATUS] = EP_UNKNOWN;
16053     for(i=0; i<nrCastlingRights; i++ ) {
16054         board[CASTLING][i] =
16055             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16056     }   /* assume possible unless obviously impossible */
16057     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16058     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16059     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16060                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16061     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16062     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16063     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16064                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16065     FENrulePlies = 0;
16066
16067     while(*p==' ') p++;
16068     if(nrCastlingRights) {
16069       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16070           /* castling indicator present, so default becomes no castlings */
16071           for(i=0; i<nrCastlingRights; i++ ) {
16072                  board[CASTLING][i] = NoRights;
16073           }
16074       }
16075       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16076              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16077              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16078              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16079         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16080
16081         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16082             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16083             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16084         }
16085         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16086             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16087         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16088                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16089         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16090                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16091         switch(c) {
16092           case'K':
16093               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16094               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16095               board[CASTLING][2] = whiteKingFile;
16096               break;
16097           case'Q':
16098               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16099               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16100               board[CASTLING][2] = whiteKingFile;
16101               break;
16102           case'k':
16103               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16104               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16105               board[CASTLING][5] = blackKingFile;
16106               break;
16107           case'q':
16108               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16109               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16110               board[CASTLING][5] = blackKingFile;
16111           case '-':
16112               break;
16113           default: /* FRC castlings */
16114               if(c >= 'a') { /* black rights */
16115                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16116                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16117                   if(i == BOARD_RGHT) break;
16118                   board[CASTLING][5] = i;
16119                   c -= AAA;
16120                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16121                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16122                   if(c > i)
16123                       board[CASTLING][3] = c;
16124                   else
16125                       board[CASTLING][4] = c;
16126               } else { /* white rights */
16127                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16128                     if(board[0][i] == WhiteKing) break;
16129                   if(i == BOARD_RGHT) break;
16130                   board[CASTLING][2] = i;
16131                   c -= AAA - 'a' + 'A';
16132                   if(board[0][c] >= WhiteKing) break;
16133                   if(c > i)
16134                       board[CASTLING][0] = c;
16135                   else
16136                       board[CASTLING][1] = c;
16137               }
16138         }
16139       }
16140       for(i=0; i<nrCastlingRights; i++)
16141         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16142     if (appData.debugMode) {
16143         fprintf(debugFP, "FEN castling rights:");
16144         for(i=0; i<nrCastlingRights; i++)
16145         fprintf(debugFP, " %d", board[CASTLING][i]);
16146         fprintf(debugFP, "\n");
16147     }
16148
16149       while(*p==' ') p++;
16150     }
16151
16152     /* read e.p. field in games that know e.p. capture */
16153     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16154        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16155       if(*p=='-') {
16156         p++; board[EP_STATUS] = EP_NONE;
16157       } else {
16158          char c = *p++ - AAA;
16159
16160          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16161          if(*p >= '0' && *p <='9') p++;
16162          board[EP_STATUS] = c;
16163       }
16164     }
16165
16166
16167     if(sscanf(p, "%d", &i) == 1) {
16168         FENrulePlies = i; /* 50-move ply counter */
16169         /* (The move number is still ignored)    */
16170     }
16171
16172     return TRUE;
16173 }
16174
16175 void
16176 EditPositionPasteFEN(char *fen)
16177 {
16178   if (fen != NULL) {
16179     Board initial_position;
16180
16181     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16182       DisplayError(_("Bad FEN position in clipboard"), 0);
16183       return ;
16184     } else {
16185       int savedBlackPlaysFirst = blackPlaysFirst;
16186       EditPositionEvent();
16187       blackPlaysFirst = savedBlackPlaysFirst;
16188       CopyBoard(boards[0], initial_position);
16189       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16190       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16191       DisplayBothClocks();
16192       DrawPosition(FALSE, boards[currentMove]);
16193     }
16194   }
16195 }
16196
16197 static char cseq[12] = "\\   ";
16198
16199 Boolean set_cont_sequence(char *new_seq)
16200 {
16201     int len;
16202     Boolean ret;
16203
16204     // handle bad attempts to set the sequence
16205         if (!new_seq)
16206                 return 0; // acceptable error - no debug
16207
16208     len = strlen(new_seq);
16209     ret = (len > 0) && (len < sizeof(cseq));
16210     if (ret)
16211       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16212     else if (appData.debugMode)
16213       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16214     return ret;
16215 }
16216
16217 /*
16218     reformat a source message so words don't cross the width boundary.  internal
16219     newlines are not removed.  returns the wrapped size (no null character unless
16220     included in source message).  If dest is NULL, only calculate the size required
16221     for the dest buffer.  lp argument indicats line position upon entry, and it's
16222     passed back upon exit.
16223 */
16224 int wrap(char *dest, char *src, int count, int width, int *lp)
16225 {
16226     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16227
16228     cseq_len = strlen(cseq);
16229     old_line = line = *lp;
16230     ansi = len = clen = 0;
16231
16232     for (i=0; i < count; i++)
16233     {
16234         if (src[i] == '\033')
16235             ansi = 1;
16236
16237         // if we hit the width, back up
16238         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16239         {
16240             // store i & len in case the word is too long
16241             old_i = i, old_len = len;
16242
16243             // find the end of the last word
16244             while (i && src[i] != ' ' && src[i] != '\n')
16245             {
16246                 i--;
16247                 len--;
16248             }
16249
16250             // word too long?  restore i & len before splitting it
16251             if ((old_i-i+clen) >= width)
16252             {
16253                 i = old_i;
16254                 len = old_len;
16255             }
16256
16257             // extra space?
16258             if (i && src[i-1] == ' ')
16259                 len--;
16260
16261             if (src[i] != ' ' && src[i] != '\n')
16262             {
16263                 i--;
16264                 if (len)
16265                     len--;
16266             }
16267
16268             // now append the newline and continuation sequence
16269             if (dest)
16270                 dest[len] = '\n';
16271             len++;
16272             if (dest)
16273                 strncpy(dest+len, cseq, cseq_len);
16274             len += cseq_len;
16275             line = cseq_len;
16276             clen = cseq_len;
16277             continue;
16278         }
16279
16280         if (dest)
16281             dest[len] = src[i];
16282         len++;
16283         if (!ansi)
16284             line++;
16285         if (src[i] == '\n')
16286             line = 0;
16287         if (src[i] == 'm')
16288             ansi = 0;
16289     }
16290     if (dest && appData.debugMode)
16291     {
16292         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16293             count, width, line, len, *lp);
16294         show_bytes(debugFP, src, count);
16295         fprintf(debugFP, "\ndest: ");
16296         show_bytes(debugFP, dest, len);
16297         fprintf(debugFP, "\n");
16298     }
16299     *lp = dest ? line : old_line;
16300
16301     return len;
16302 }
16303
16304 // [HGM] vari: routines for shelving variations
16305
16306 void
16307 PushInner(int firstMove, int lastMove)
16308 {
16309         int i, j, nrMoves = lastMove - firstMove;
16310
16311         // push current tail of game on stack
16312         savedResult[storedGames] = gameInfo.result;
16313         savedDetails[storedGames] = gameInfo.resultDetails;
16314         gameInfo.resultDetails = NULL;
16315         savedFirst[storedGames] = firstMove;
16316         savedLast [storedGames] = lastMove;
16317         savedFramePtr[storedGames] = framePtr;
16318         framePtr -= nrMoves; // reserve space for the boards
16319         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16320             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16321             for(j=0; j<MOVE_LEN; j++)
16322                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16323             for(j=0; j<2*MOVE_LEN; j++)
16324                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16325             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16326             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16327             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16328             pvInfoList[firstMove+i-1].depth = 0;
16329             commentList[framePtr+i] = commentList[firstMove+i];
16330             commentList[firstMove+i] = NULL;
16331         }
16332
16333         storedGames++;
16334         forwardMostMove = firstMove; // truncate game so we can start variation
16335 }
16336
16337 void
16338 PushTail(int firstMove, int lastMove)
16339 {
16340         if(appData.icsActive) { // only in local mode
16341                 forwardMostMove = currentMove; // mimic old ICS behavior
16342                 return;
16343         }
16344         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16345
16346         PushInner(firstMove, lastMove);
16347         if(storedGames == 1) GreyRevert(FALSE);
16348 }
16349
16350 void
16351 PopInner(Boolean annotate)
16352 {
16353         int i, j, nrMoves;
16354         char buf[8000], moveBuf[20];
16355
16356         storedGames--;
16357         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16358         nrMoves = savedLast[storedGames] - currentMove;
16359         if(annotate) {
16360                 int cnt = 10;
16361                 if(!WhiteOnMove(currentMove))
16362                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16363                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16364                 for(i=currentMove; i<forwardMostMove; i++) {
16365                         if(WhiteOnMove(i))
16366                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16367                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16368                         strcat(buf, moveBuf);
16369                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16370                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16371                 }
16372                 strcat(buf, ")");
16373         }
16374         for(i=1; i<=nrMoves; i++) { // copy last variation back
16375             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16376             for(j=0; j<MOVE_LEN; j++)
16377                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16378             for(j=0; j<2*MOVE_LEN; j++)
16379                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16380             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16381             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16382             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16383             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16384             commentList[currentMove+i] = commentList[framePtr+i];
16385             commentList[framePtr+i] = NULL;
16386         }
16387         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16388         framePtr = savedFramePtr[storedGames];
16389         gameInfo.result = savedResult[storedGames];
16390         if(gameInfo.resultDetails != NULL) {
16391             free(gameInfo.resultDetails);
16392       }
16393         gameInfo.resultDetails = savedDetails[storedGames];
16394         forwardMostMove = currentMove + nrMoves;
16395 }
16396
16397 Boolean
16398 PopTail(Boolean annotate)
16399 {
16400         if(appData.icsActive) return FALSE; // only in local mode
16401         if(!storedGames) return FALSE; // sanity
16402         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16403
16404         PopInner(annotate);
16405
16406         if(storedGames == 0) GreyRevert(TRUE);
16407         return TRUE;
16408 }
16409
16410 void
16411 CleanupTail()
16412 {       // remove all shelved variations
16413         int i;
16414         for(i=0; i<storedGames; i++) {
16415             if(savedDetails[i])
16416                 free(savedDetails[i]);
16417             savedDetails[i] = NULL;
16418         }
16419         for(i=framePtr; i<MAX_MOVES; i++) {
16420                 if(commentList[i]) free(commentList[i]);
16421                 commentList[i] = NULL;
16422         }
16423         framePtr = MAX_MOVES-1;
16424         storedGames = 0;
16425 }
16426
16427 void
16428 LoadVariation(int index, char *text)
16429 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16430         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16431         int level = 0, move;
16432
16433         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16434         // first find outermost bracketing variation
16435         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16436             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16437                 if(*p == '{') wait = '}'; else
16438                 if(*p == '[') wait = ']'; else
16439                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16440                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16441             }
16442             if(*p == wait) wait = NULLCHAR; // closing ]} found
16443             p++;
16444         }
16445         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16446         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16447         end[1] = NULLCHAR; // clip off comment beyond variation
16448         ToNrEvent(currentMove-1);
16449         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16450         // kludge: use ParsePV() to append variation to game
16451         move = currentMove;
16452         ParsePV(start, TRUE, TRUE);
16453         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16454         ClearPremoveHighlights();
16455         CommentPopDown();
16456         ToNrEvent(currentMove+1);
16457 }
16458