Update window itle after last game of match
[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         DisplayNote(message + 9);
8042         return;
8043     }
8044     if (!strncmp(message, "tellusererror ", 14)) {
8045         cps->userError = 1;
8046         if(message[14] == '\\' && message[15] == '\\')
8047             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8048         DisplayError(message + 14, 0);
8049         return;
8050     }
8051     if (!strncmp(message, "tellopponent ", 13)) {
8052       if (appData.icsActive) {
8053         if (loggedOn) {
8054           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8055           SendToICS(buf1);
8056         }
8057       } else {
8058         DisplayNote(message + 13);
8059       }
8060       return;
8061     }
8062     if (!strncmp(message, "tellothers ", 11)) {
8063       if (appData.icsActive) {
8064         if (loggedOn) {
8065           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8066           SendToICS(buf1);
8067         }
8068       }
8069       return;
8070     }
8071     if (!strncmp(message, "tellall ", 8)) {
8072       if (appData.icsActive) {
8073         if (loggedOn) {
8074           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8075           SendToICS(buf1);
8076         }
8077       } else {
8078         DisplayNote(message + 8);
8079       }
8080       return;
8081     }
8082     if (strncmp(message, "warning", 7) == 0) {
8083         /* Undocumented feature, use tellusererror in new code */
8084         DisplayError(message, 0);
8085         return;
8086     }
8087     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8088         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8089         strcat(realname, " query");
8090         AskQuestion(realname, buf2, buf1, cps->pr);
8091         return;
8092     }
8093     /* Commands from the engine directly to ICS.  We don't allow these to be
8094      *  sent until we are logged on. Crafty kibitzes have been known to
8095      *  interfere with the login process.
8096      */
8097     if (loggedOn) {
8098         if (!strncmp(message, "tellics ", 8)) {
8099             SendToICS(message + 8);
8100             SendToICS("\n");
8101             return;
8102         }
8103         if (!strncmp(message, "tellicsnoalias ", 15)) {
8104             SendToICS(ics_prefix);
8105             SendToICS(message + 15);
8106             SendToICS("\n");
8107             return;
8108         }
8109         /* The following are for backward compatibility only */
8110         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8111             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8112             SendToICS(ics_prefix);
8113             SendToICS(message);
8114             SendToICS("\n");
8115             return;
8116         }
8117     }
8118     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8119         return;
8120     }
8121     /*
8122      * If the move is illegal, cancel it and redraw the board.
8123      * Also deal with other error cases.  Matching is rather loose
8124      * here to accommodate engines written before the spec.
8125      */
8126     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8127         strncmp(message, "Error", 5) == 0) {
8128         if (StrStr(message, "name") ||
8129             StrStr(message, "rating") || StrStr(message, "?") ||
8130             StrStr(message, "result") || StrStr(message, "board") ||
8131             StrStr(message, "bk") || StrStr(message, "computer") ||
8132             StrStr(message, "variant") || StrStr(message, "hint") ||
8133             StrStr(message, "random") || StrStr(message, "depth") ||
8134             StrStr(message, "accepted")) {
8135             return;
8136         }
8137         if (StrStr(message, "protover")) {
8138           /* Program is responding to input, so it's apparently done
8139              initializing, and this error message indicates it is
8140              protocol version 1.  So we don't need to wait any longer
8141              for it to initialize and send feature commands. */
8142           FeatureDone(cps, 1);
8143           cps->protocolVersion = 1;
8144           return;
8145         }
8146         cps->maybeThinking = FALSE;
8147
8148         if (StrStr(message, "draw")) {
8149             /* Program doesn't have "draw" command */
8150             cps->sendDrawOffers = 0;
8151             return;
8152         }
8153         if (cps->sendTime != 1 &&
8154             (StrStr(message, "time") || StrStr(message, "otim"))) {
8155           /* Program apparently doesn't have "time" or "otim" command */
8156           cps->sendTime = 0;
8157           return;
8158         }
8159         if (StrStr(message, "analyze")) {
8160             cps->analysisSupport = FALSE;
8161             cps->analyzing = FALSE;
8162             Reset(FALSE, TRUE);
8163             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8164             DisplayError(buf2, 0);
8165             return;
8166         }
8167         if (StrStr(message, "(no matching move)st")) {
8168           /* Special kludge for GNU Chess 4 only */
8169           cps->stKludge = TRUE;
8170           SendTimeControl(cps, movesPerSession, timeControl,
8171                           timeIncrement, appData.searchDepth,
8172                           searchTime);
8173           return;
8174         }
8175         if (StrStr(message, "(no matching move)sd")) {
8176           /* Special kludge for GNU Chess 4 only */
8177           cps->sdKludge = TRUE;
8178           SendTimeControl(cps, movesPerSession, timeControl,
8179                           timeIncrement, appData.searchDepth,
8180                           searchTime);
8181           return;
8182         }
8183         if (!StrStr(message, "llegal")) {
8184             return;
8185         }
8186         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8187             gameMode == IcsIdle) return;
8188         if (forwardMostMove <= backwardMostMove) return;
8189         if (pausing) PauseEvent();
8190       if(appData.forceIllegal) {
8191             // [HGM] illegal: machine refused move; force position after move into it
8192           SendToProgram("force\n", cps);
8193           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8194                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8195                 // when black is to move, while there might be nothing on a2 or black
8196                 // might already have the move. So send the board as if white has the move.
8197                 // But first we must change the stm of the engine, as it refused the last move
8198                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8199                 if(WhiteOnMove(forwardMostMove)) {
8200                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8201                     SendBoard(cps, forwardMostMove); // kludgeless board
8202                 } else {
8203                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8204                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8205                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8206                 }
8207           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8208             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8209                  gameMode == TwoMachinesPlay)
8210               SendToProgram("go\n", cps);
8211             return;
8212       } else
8213         if (gameMode == PlayFromGameFile) {
8214             /* Stop reading this game file */
8215             gameMode = EditGame;
8216             ModeHighlight();
8217         }
8218         /* [HGM] illegal-move claim should forfeit game when Xboard */
8219         /* only passes fully legal moves                            */
8220         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8221             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8222                                 "False illegal-move claim", GE_XBOARD );
8223             return; // do not take back move we tested as valid
8224         }
8225         currentMove = forwardMostMove-1;
8226         DisplayMove(currentMove-1); /* before DisplayMoveError */
8227         SwitchClocks(forwardMostMove-1); // [HGM] race
8228         DisplayBothClocks();
8229         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8230                 parseList[currentMove], _(cps->which));
8231         DisplayMoveError(buf1);
8232         DrawPosition(FALSE, boards[currentMove]);
8233         return;
8234     }
8235     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8236         /* Program has a broken "time" command that
8237            outputs a string not ending in newline.
8238            Don't use it. */
8239         cps->sendTime = 0;
8240     }
8241
8242     /*
8243      * If chess program startup fails, exit with an error message.
8244      * Attempts to recover here are futile.
8245      */
8246     if ((StrStr(message, "unknown host") != NULL)
8247         || (StrStr(message, "No remote directory") != NULL)
8248         || (StrStr(message, "not found") != NULL)
8249         || (StrStr(message, "No such file") != NULL)
8250         || (StrStr(message, "can't alloc") != NULL)
8251         || (StrStr(message, "Permission denied") != NULL)) {
8252
8253         cps->maybeThinking = FALSE;
8254         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8255                 _(cps->which), cps->program, cps->host, message);
8256         RemoveInputSource(cps->isr);
8257         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8258             if(cps == &first) appData.noChessProgram = TRUE;
8259             DisplayError(buf1, 0);
8260         }
8261         return;
8262     }
8263
8264     /*
8265      * Look for hint output
8266      */
8267     if (sscanf(message, "Hint: %s", buf1) == 1) {
8268         if (cps == &first && hintRequested) {
8269             hintRequested = FALSE;
8270             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8271                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8272                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8273                                     PosFlags(forwardMostMove),
8274                                     fromY, fromX, toY, toX, promoChar, buf1);
8275                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8276                 DisplayInformation(buf2);
8277             } else {
8278                 /* Hint move could not be parsed!? */
8279               snprintf(buf2, sizeof(buf2),
8280                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8281                         buf1, _(cps->which));
8282                 DisplayError(buf2, 0);
8283             }
8284         } else {
8285           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8286         }
8287         return;
8288     }
8289
8290     /*
8291      * Ignore other messages if game is not in progress
8292      */
8293     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8294         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8295
8296     /*
8297      * look for win, lose, draw, or draw offer
8298      */
8299     if (strncmp(message, "1-0", 3) == 0) {
8300         char *p, *q, *r = "";
8301         p = strchr(message, '{');
8302         if (p) {
8303             q = strchr(p, '}');
8304             if (q) {
8305                 *q = NULLCHAR;
8306                 r = p + 1;
8307             }
8308         }
8309         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8310         return;
8311     } else if (strncmp(message, "0-1", 3) == 0) {
8312         char *p, *q, *r = "";
8313         p = strchr(message, '{');
8314         if (p) {
8315             q = strchr(p, '}');
8316             if (q) {
8317                 *q = NULLCHAR;
8318                 r = p + 1;
8319             }
8320         }
8321         /* Kludge for Arasan 4.1 bug */
8322         if (strcmp(r, "Black resigns") == 0) {
8323             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8324             return;
8325         }
8326         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8327         return;
8328     } else if (strncmp(message, "1/2", 3) == 0) {
8329         char *p, *q, *r = "";
8330         p = strchr(message, '{');
8331         if (p) {
8332             q = strchr(p, '}');
8333             if (q) {
8334                 *q = NULLCHAR;
8335                 r = p + 1;
8336             }
8337         }
8338
8339         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8340         return;
8341
8342     } else if (strncmp(message, "White resign", 12) == 0) {
8343         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8344         return;
8345     } else if (strncmp(message, "Black resign", 12) == 0) {
8346         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8347         return;
8348     } else if (strncmp(message, "White matches", 13) == 0 ||
8349                strncmp(message, "Black matches", 13) == 0   ) {
8350         /* [HGM] ignore GNUShogi noises */
8351         return;
8352     } else if (strncmp(message, "White", 5) == 0 &&
8353                message[5] != '(' &&
8354                StrStr(message, "Black") == NULL) {
8355         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8356         return;
8357     } else if (strncmp(message, "Black", 5) == 0 &&
8358                message[5] != '(') {
8359         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8360         return;
8361     } else if (strcmp(message, "resign") == 0 ||
8362                strcmp(message, "computer resigns") == 0) {
8363         switch (gameMode) {
8364           case MachinePlaysBlack:
8365           case IcsPlayingBlack:
8366             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8367             break;
8368           case MachinePlaysWhite:
8369           case IcsPlayingWhite:
8370             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8371             break;
8372           case TwoMachinesPlay:
8373             if (cps->twoMachinesColor[0] == 'w')
8374               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8375             else
8376               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8377             break;
8378           default:
8379             /* can't happen */
8380             break;
8381         }
8382         return;
8383     } else if (strncmp(message, "opponent mates", 14) == 0) {
8384         switch (gameMode) {
8385           case MachinePlaysBlack:
8386           case IcsPlayingBlack:
8387             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8388             break;
8389           case MachinePlaysWhite:
8390           case IcsPlayingWhite:
8391             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8392             break;
8393           case TwoMachinesPlay:
8394             if (cps->twoMachinesColor[0] == 'w')
8395               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8396             else
8397               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8398             break;
8399           default:
8400             /* can't happen */
8401             break;
8402         }
8403         return;
8404     } else if (strncmp(message, "computer mates", 14) == 0) {
8405         switch (gameMode) {
8406           case MachinePlaysBlack:
8407           case IcsPlayingBlack:
8408             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8409             break;
8410           case MachinePlaysWhite:
8411           case IcsPlayingWhite:
8412             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8413             break;
8414           case TwoMachinesPlay:
8415             if (cps->twoMachinesColor[0] == 'w')
8416               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8417             else
8418               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8419             break;
8420           default:
8421             /* can't happen */
8422             break;
8423         }
8424         return;
8425     } else if (strncmp(message, "checkmate", 9) == 0) {
8426         if (WhiteOnMove(forwardMostMove)) {
8427             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8428         } else {
8429             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8430         }
8431         return;
8432     } else if (strstr(message, "Draw") != NULL ||
8433                strstr(message, "game is a draw") != NULL) {
8434         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8435         return;
8436     } else if (strstr(message, "offer") != NULL &&
8437                strstr(message, "draw") != NULL) {
8438 #if ZIPPY
8439         if (appData.zippyPlay && first.initDone) {
8440             /* Relay offer to ICS */
8441             SendToICS(ics_prefix);
8442             SendToICS("draw\n");
8443         }
8444 #endif
8445         cps->offeredDraw = 2; /* valid until this engine moves twice */
8446         if (gameMode == TwoMachinesPlay) {
8447             if (cps->other->offeredDraw) {
8448                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8449             /* [HGM] in two-machine mode we delay relaying draw offer      */
8450             /* until after we also have move, to see if it is really claim */
8451             }
8452         } else if (gameMode == MachinePlaysWhite ||
8453                    gameMode == MachinePlaysBlack) {
8454           if (userOfferedDraw) {
8455             DisplayInformation(_("Machine accepts your draw offer"));
8456             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8457           } else {
8458             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8459           }
8460         }
8461     }
8462
8463
8464     /*
8465      * Look for thinking output
8466      */
8467     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8468           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8469                                 ) {
8470         int plylev, mvleft, mvtot, curscore, time;
8471         char mvname[MOVE_LEN];
8472         u64 nodes; // [DM]
8473         char plyext;
8474         int ignore = FALSE;
8475         int prefixHint = FALSE;
8476         mvname[0] = NULLCHAR;
8477
8478         switch (gameMode) {
8479           case MachinePlaysBlack:
8480           case IcsPlayingBlack:
8481             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8482             break;
8483           case MachinePlaysWhite:
8484           case IcsPlayingWhite:
8485             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8486             break;
8487           case AnalyzeMode:
8488           case AnalyzeFile:
8489             break;
8490           case IcsObserving: /* [DM] icsEngineAnalyze */
8491             if (!appData.icsEngineAnalyze) ignore = TRUE;
8492             break;
8493           case TwoMachinesPlay:
8494             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8495                 ignore = TRUE;
8496             }
8497             break;
8498           default:
8499             ignore = TRUE;
8500             break;
8501         }
8502
8503         if (!ignore) {
8504             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8505             buf1[0] = NULLCHAR;
8506             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8507                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8508
8509                 if (plyext != ' ' && plyext != '\t') {
8510                     time *= 100;
8511                 }
8512
8513                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8514                 if( cps->scoreIsAbsolute &&
8515                     ( gameMode == MachinePlaysBlack ||
8516                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8517                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8518                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8519                      !WhiteOnMove(currentMove)
8520                     ) )
8521                 {
8522                     curscore = -curscore;
8523                 }
8524
8525
8526                 tempStats.depth = plylev;
8527                 tempStats.nodes = nodes;
8528                 tempStats.time = time;
8529                 tempStats.score = curscore;
8530                 tempStats.got_only_move = 0;
8531
8532                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8533                         int ticklen;
8534
8535                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8536                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8537                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8538                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8539                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8540                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8541                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8542                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8543                 }
8544
8545                 /* Buffer overflow protection */
8546                 if (buf1[0] != NULLCHAR) {
8547                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8548                         && appData.debugMode) {
8549                         fprintf(debugFP,
8550                                 "PV is too long; using the first %u bytes.\n",
8551                                 (unsigned) sizeof(tempStats.movelist) - 1);
8552                     }
8553
8554                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8555                 } else {
8556                     sprintf(tempStats.movelist, " no PV\n");
8557                 }
8558
8559                 if (tempStats.seen_stat) {
8560                     tempStats.ok_to_send = 1;
8561                 }
8562
8563                 if (strchr(tempStats.movelist, '(') != NULL) {
8564                     tempStats.line_is_book = 1;
8565                     tempStats.nr_moves = 0;
8566                     tempStats.moves_left = 0;
8567                 } else {
8568                     tempStats.line_is_book = 0;
8569                 }
8570
8571                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8572                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8573
8574                 SendProgramStatsToFrontend( cps, &tempStats );
8575
8576                 /*
8577                     [AS] Protect the thinkOutput buffer from overflow... this
8578                     is only useful if buf1 hasn't overflowed first!
8579                 */
8580                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8581                          plylev,
8582                          (gameMode == TwoMachinesPlay ?
8583                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8584                          ((double) curscore) / 100.0,
8585                          prefixHint ? lastHint : "",
8586                          prefixHint ? " " : "" );
8587
8588                 if( buf1[0] != NULLCHAR ) {
8589                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8590
8591                     if( strlen(buf1) > max_len ) {
8592                         if( appData.debugMode) {
8593                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8594                         }
8595                         buf1[max_len+1] = '\0';
8596                     }
8597
8598                     strcat( thinkOutput, buf1 );
8599                 }
8600
8601                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8602                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8603                     DisplayMove(currentMove - 1);
8604                 }
8605                 return;
8606
8607             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8608                 /* crafty (9.25+) says "(only move) <move>"
8609                  * if there is only 1 legal move
8610                  */
8611                 sscanf(p, "(only move) %s", buf1);
8612                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8613                 sprintf(programStats.movelist, "%s (only move)", buf1);
8614                 programStats.depth = 1;
8615                 programStats.nr_moves = 1;
8616                 programStats.moves_left = 1;
8617                 programStats.nodes = 1;
8618                 programStats.time = 1;
8619                 programStats.got_only_move = 1;
8620
8621                 /* Not really, but we also use this member to
8622                    mean "line isn't going to change" (Crafty
8623                    isn't searching, so stats won't change) */
8624                 programStats.line_is_book = 1;
8625
8626                 SendProgramStatsToFrontend( cps, &programStats );
8627
8628                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8629                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8630                     DisplayMove(currentMove - 1);
8631                 }
8632                 return;
8633             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8634                               &time, &nodes, &plylev, &mvleft,
8635                               &mvtot, mvname) >= 5) {
8636                 /* The stat01: line is from Crafty (9.29+) in response
8637                    to the "." command */
8638                 programStats.seen_stat = 1;
8639                 cps->maybeThinking = TRUE;
8640
8641                 if (programStats.got_only_move || !appData.periodicUpdates)
8642                   return;
8643
8644                 programStats.depth = plylev;
8645                 programStats.time = time;
8646                 programStats.nodes = nodes;
8647                 programStats.moves_left = mvleft;
8648                 programStats.nr_moves = mvtot;
8649                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8650                 programStats.ok_to_send = 1;
8651                 programStats.movelist[0] = '\0';
8652
8653                 SendProgramStatsToFrontend( cps, &programStats );
8654
8655                 return;
8656
8657             } else if (strncmp(message,"++",2) == 0) {
8658                 /* Crafty 9.29+ outputs this */
8659                 programStats.got_fail = 2;
8660                 return;
8661
8662             } else if (strncmp(message,"--",2) == 0) {
8663                 /* Crafty 9.29+ outputs this */
8664                 programStats.got_fail = 1;
8665                 return;
8666
8667             } else if (thinkOutput[0] != NULLCHAR &&
8668                        strncmp(message, "    ", 4) == 0) {
8669                 unsigned message_len;
8670
8671                 p = message;
8672                 while (*p && *p == ' ') p++;
8673
8674                 message_len = strlen( p );
8675
8676                 /* [AS] Avoid buffer overflow */
8677                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8678                     strcat(thinkOutput, " ");
8679                     strcat(thinkOutput, p);
8680                 }
8681
8682                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8683                     strcat(programStats.movelist, " ");
8684                     strcat(programStats.movelist, p);
8685                 }
8686
8687                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8688                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8689                     DisplayMove(currentMove - 1);
8690                 }
8691                 return;
8692             }
8693         }
8694         else {
8695             buf1[0] = NULLCHAR;
8696
8697             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8698                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8699             {
8700                 ChessProgramStats cpstats;
8701
8702                 if (plyext != ' ' && plyext != '\t') {
8703                     time *= 100;
8704                 }
8705
8706                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8707                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8708                     curscore = -curscore;
8709                 }
8710
8711                 cpstats.depth = plylev;
8712                 cpstats.nodes = nodes;
8713                 cpstats.time = time;
8714                 cpstats.score = curscore;
8715                 cpstats.got_only_move = 0;
8716                 cpstats.movelist[0] = '\0';
8717
8718                 if (buf1[0] != NULLCHAR) {
8719                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8720                 }
8721
8722                 cpstats.ok_to_send = 0;
8723                 cpstats.line_is_book = 0;
8724                 cpstats.nr_moves = 0;
8725                 cpstats.moves_left = 0;
8726
8727                 SendProgramStatsToFrontend( cps, &cpstats );
8728             }
8729         }
8730     }
8731 }
8732
8733
8734 /* Parse a game score from the character string "game", and
8735    record it as the history of the current game.  The game
8736    score is NOT assumed to start from the standard position.
8737    The display is not updated in any way.
8738    */
8739 void
8740 ParseGameHistory(game)
8741      char *game;
8742 {
8743     ChessMove moveType;
8744     int fromX, fromY, toX, toY, boardIndex;
8745     char promoChar;
8746     char *p, *q;
8747     char buf[MSG_SIZ];
8748
8749     if (appData.debugMode)
8750       fprintf(debugFP, "Parsing game history: %s\n", game);
8751
8752     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8753     gameInfo.site = StrSave(appData.icsHost);
8754     gameInfo.date = PGNDate();
8755     gameInfo.round = StrSave("-");
8756
8757     /* Parse out names of players */
8758     while (*game == ' ') game++;
8759     p = buf;
8760     while (*game != ' ') *p++ = *game++;
8761     *p = NULLCHAR;
8762     gameInfo.white = StrSave(buf);
8763     while (*game == ' ') game++;
8764     p = buf;
8765     while (*game != ' ' && *game != '\n') *p++ = *game++;
8766     *p = NULLCHAR;
8767     gameInfo.black = StrSave(buf);
8768
8769     /* Parse moves */
8770     boardIndex = blackPlaysFirst ? 1 : 0;
8771     yynewstr(game);
8772     for (;;) {
8773         yyboardindex = boardIndex;
8774         moveType = (ChessMove) Myylex();
8775         switch (moveType) {
8776           case IllegalMove:             /* maybe suicide chess, etc. */
8777   if (appData.debugMode) {
8778     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8779     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8780     setbuf(debugFP, NULL);
8781   }
8782           case WhitePromotion:
8783           case BlackPromotion:
8784           case WhiteNonPromotion:
8785           case BlackNonPromotion:
8786           case NormalMove:
8787           case WhiteCapturesEnPassant:
8788           case BlackCapturesEnPassant:
8789           case WhiteKingSideCastle:
8790           case WhiteQueenSideCastle:
8791           case BlackKingSideCastle:
8792           case BlackQueenSideCastle:
8793           case WhiteKingSideCastleWild:
8794           case WhiteQueenSideCastleWild:
8795           case BlackKingSideCastleWild:
8796           case BlackQueenSideCastleWild:
8797           /* PUSH Fabien */
8798           case WhiteHSideCastleFR:
8799           case WhiteASideCastleFR:
8800           case BlackHSideCastleFR:
8801           case BlackASideCastleFR:
8802           /* POP Fabien */
8803             fromX = currentMoveString[0] - AAA;
8804             fromY = currentMoveString[1] - ONE;
8805             toX = currentMoveString[2] - AAA;
8806             toY = currentMoveString[3] - ONE;
8807             promoChar = currentMoveString[4];
8808             break;
8809           case WhiteDrop:
8810           case BlackDrop:
8811             fromX = moveType == WhiteDrop ?
8812               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8813             (int) CharToPiece(ToLower(currentMoveString[0]));
8814             fromY = DROP_RANK;
8815             toX = currentMoveString[2] - AAA;
8816             toY = currentMoveString[3] - ONE;
8817             promoChar = NULLCHAR;
8818             break;
8819           case AmbiguousMove:
8820             /* bug? */
8821             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8822   if (appData.debugMode) {
8823     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8824     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8825     setbuf(debugFP, NULL);
8826   }
8827             DisplayError(buf, 0);
8828             return;
8829           case ImpossibleMove:
8830             /* bug? */
8831             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8832   if (appData.debugMode) {
8833     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8834     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8835     setbuf(debugFP, NULL);
8836   }
8837             DisplayError(buf, 0);
8838             return;
8839           case EndOfFile:
8840             if (boardIndex < backwardMostMove) {
8841                 /* Oops, gap.  How did that happen? */
8842                 DisplayError(_("Gap in move list"), 0);
8843                 return;
8844             }
8845             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8846             if (boardIndex > forwardMostMove) {
8847                 forwardMostMove = boardIndex;
8848             }
8849             return;
8850           case ElapsedTime:
8851             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8852                 strcat(parseList[boardIndex-1], " ");
8853                 strcat(parseList[boardIndex-1], yy_text);
8854             }
8855             continue;
8856           case Comment:
8857           case PGNTag:
8858           case NAG:
8859           default:
8860             /* ignore */
8861             continue;
8862           case WhiteWins:
8863           case BlackWins:
8864           case GameIsDrawn:
8865           case GameUnfinished:
8866             if (gameMode == IcsExamining) {
8867                 if (boardIndex < backwardMostMove) {
8868                     /* Oops, gap.  How did that happen? */
8869                     return;
8870                 }
8871                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8872                 return;
8873             }
8874             gameInfo.result = moveType;
8875             p = strchr(yy_text, '{');
8876             if (p == NULL) p = strchr(yy_text, '(');
8877             if (p == NULL) {
8878                 p = yy_text;
8879                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8880             } else {
8881                 q = strchr(p, *p == '{' ? '}' : ')');
8882                 if (q != NULL) *q = NULLCHAR;
8883                 p++;
8884             }
8885             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8886             gameInfo.resultDetails = StrSave(p);
8887             continue;
8888         }
8889         if (boardIndex >= forwardMostMove &&
8890             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8891             backwardMostMove = blackPlaysFirst ? 1 : 0;
8892             return;
8893         }
8894         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8895                                  fromY, fromX, toY, toX, promoChar,
8896                                  parseList[boardIndex]);
8897         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8898         /* currentMoveString is set as a side-effect of yylex */
8899         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8900         strcat(moveList[boardIndex], "\n");
8901         boardIndex++;
8902         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8903         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8904           case MT_NONE:
8905           case MT_STALEMATE:
8906           default:
8907             break;
8908           case MT_CHECK:
8909             if(gameInfo.variant != VariantShogi)
8910                 strcat(parseList[boardIndex - 1], "+");
8911             break;
8912           case MT_CHECKMATE:
8913           case MT_STAINMATE:
8914             strcat(parseList[boardIndex - 1], "#");
8915             break;
8916         }
8917     }
8918 }
8919
8920
8921 /* Apply a move to the given board  */
8922 void
8923 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8924      int fromX, fromY, toX, toY;
8925      int promoChar;
8926      Board board;
8927 {
8928   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8929   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8930
8931     /* [HGM] compute & store e.p. status and castling rights for new position */
8932     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8933
8934       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8935       oldEP = (signed char)board[EP_STATUS];
8936       board[EP_STATUS] = EP_NONE;
8937
8938       if( board[toY][toX] != EmptySquare )
8939            board[EP_STATUS] = EP_CAPTURE;
8940
8941   if (fromY == DROP_RANK) {
8942         /* must be first */
8943         piece = board[toY][toX] = (ChessSquare) fromX;
8944   } else {
8945       int i;
8946
8947       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8948            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8949                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8950       } else
8951       if( board[fromY][fromX] == WhitePawn ) {
8952            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8953                board[EP_STATUS] = EP_PAWN_MOVE;
8954            if( toY-fromY==2) {
8955                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8956                         gameInfo.variant != VariantBerolina || toX < fromX)
8957                       board[EP_STATUS] = toX | berolina;
8958                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8959                         gameInfo.variant != VariantBerolina || toX > fromX)
8960                       board[EP_STATUS] = toX;
8961            }
8962       } else
8963       if( board[fromY][fromX] == BlackPawn ) {
8964            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8965                board[EP_STATUS] = EP_PAWN_MOVE;
8966            if( toY-fromY== -2) {
8967                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8968                         gameInfo.variant != VariantBerolina || toX < fromX)
8969                       board[EP_STATUS] = toX | berolina;
8970                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8971                         gameInfo.variant != VariantBerolina || toX > fromX)
8972                       board[EP_STATUS] = toX;
8973            }
8974        }
8975
8976        for(i=0; i<nrCastlingRights; i++) {
8977            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8978               board[CASTLING][i] == toX   && castlingRank[i] == toY
8979              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8980        }
8981
8982      if (fromX == toX && fromY == toY) return;
8983
8984      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8985      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8986      if(gameInfo.variant == VariantKnightmate)
8987          king += (int) WhiteUnicorn - (int) WhiteKing;
8988
8989     /* Code added by Tord: */
8990     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8991     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8992         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8993       board[fromY][fromX] = EmptySquare;
8994       board[toY][toX] = EmptySquare;
8995       if((toX > fromX) != (piece == WhiteRook)) {
8996         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8997       } else {
8998         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8999       }
9000     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9001                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9002       board[fromY][fromX] = EmptySquare;
9003       board[toY][toX] = EmptySquare;
9004       if((toX > fromX) != (piece == BlackRook)) {
9005         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9006       } else {
9007         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9008       }
9009     /* End of code added by Tord */
9010
9011     } else if (board[fromY][fromX] == king
9012         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9013         && toY == fromY && toX > fromX+1) {
9014         board[fromY][fromX] = EmptySquare;
9015         board[toY][toX] = king;
9016         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9017         board[fromY][BOARD_RGHT-1] = EmptySquare;
9018     } else if (board[fromY][fromX] == king
9019         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9020                && toY == fromY && toX < fromX-1) {
9021         board[fromY][fromX] = EmptySquare;
9022         board[toY][toX] = king;
9023         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9024         board[fromY][BOARD_LEFT] = EmptySquare;
9025     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9026                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9027                && toY >= BOARD_HEIGHT-promoRank
9028                ) {
9029         /* white pawn promotion */
9030         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9031         if (board[toY][toX] == EmptySquare) {
9032             board[toY][toX] = WhiteQueen;
9033         }
9034         if(gameInfo.variant==VariantBughouse ||
9035            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9036             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9037         board[fromY][fromX] = EmptySquare;
9038     } else if ((fromY == BOARD_HEIGHT-4)
9039                && (toX != fromX)
9040                && gameInfo.variant != VariantXiangqi
9041                && gameInfo.variant != VariantBerolina
9042                && (board[fromY][fromX] == WhitePawn)
9043                && (board[toY][toX] == EmptySquare)) {
9044         board[fromY][fromX] = EmptySquare;
9045         board[toY][toX] = WhitePawn;
9046         captured = board[toY - 1][toX];
9047         board[toY - 1][toX] = EmptySquare;
9048     } else if ((fromY == BOARD_HEIGHT-4)
9049                && (toX == fromX)
9050                && gameInfo.variant == VariantBerolina
9051                && (board[fromY][fromX] == WhitePawn)
9052                && (board[toY][toX] == EmptySquare)) {
9053         board[fromY][fromX] = EmptySquare;
9054         board[toY][toX] = WhitePawn;
9055         if(oldEP & EP_BEROLIN_A) {
9056                 captured = board[fromY][fromX-1];
9057                 board[fromY][fromX-1] = EmptySquare;
9058         }else{  captured = board[fromY][fromX+1];
9059                 board[fromY][fromX+1] = EmptySquare;
9060         }
9061     } else if (board[fromY][fromX] == king
9062         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9063                && toY == fromY && toX > fromX+1) {
9064         board[fromY][fromX] = EmptySquare;
9065         board[toY][toX] = king;
9066         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9067         board[fromY][BOARD_RGHT-1] = EmptySquare;
9068     } else if (board[fromY][fromX] == king
9069         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9070                && toY == fromY && toX < fromX-1) {
9071         board[fromY][fromX] = EmptySquare;
9072         board[toY][toX] = king;
9073         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9074         board[fromY][BOARD_LEFT] = EmptySquare;
9075     } else if (fromY == 7 && fromX == 3
9076                && board[fromY][fromX] == BlackKing
9077                && toY == 7 && toX == 5) {
9078         board[fromY][fromX] = EmptySquare;
9079         board[toY][toX] = BlackKing;
9080         board[fromY][7] = EmptySquare;
9081         board[toY][4] = BlackRook;
9082     } else if (fromY == 7 && fromX == 3
9083                && board[fromY][fromX] == BlackKing
9084                && toY == 7 && toX == 1) {
9085         board[fromY][fromX] = EmptySquare;
9086         board[toY][toX] = BlackKing;
9087         board[fromY][0] = EmptySquare;
9088         board[toY][2] = BlackRook;
9089     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9090                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9091                && toY < promoRank
9092                ) {
9093         /* black pawn promotion */
9094         board[toY][toX] = CharToPiece(ToLower(promoChar));
9095         if (board[toY][toX] == EmptySquare) {
9096             board[toY][toX] = BlackQueen;
9097         }
9098         if(gameInfo.variant==VariantBughouse ||
9099            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9100             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9101         board[fromY][fromX] = EmptySquare;
9102     } else if ((fromY == 3)
9103                && (toX != fromX)
9104                && gameInfo.variant != VariantXiangqi
9105                && gameInfo.variant != VariantBerolina
9106                && (board[fromY][fromX] == BlackPawn)
9107                && (board[toY][toX] == EmptySquare)) {
9108         board[fromY][fromX] = EmptySquare;
9109         board[toY][toX] = BlackPawn;
9110         captured = board[toY + 1][toX];
9111         board[toY + 1][toX] = EmptySquare;
9112     } else if ((fromY == 3)
9113                && (toX == fromX)
9114                && gameInfo.variant == VariantBerolina
9115                && (board[fromY][fromX] == BlackPawn)
9116                && (board[toY][toX] == EmptySquare)) {
9117         board[fromY][fromX] = EmptySquare;
9118         board[toY][toX] = BlackPawn;
9119         if(oldEP & EP_BEROLIN_A) {
9120                 captured = board[fromY][fromX-1];
9121                 board[fromY][fromX-1] = EmptySquare;
9122         }else{  captured = board[fromY][fromX+1];
9123                 board[fromY][fromX+1] = EmptySquare;
9124         }
9125     } else {
9126         board[toY][toX] = board[fromY][fromX];
9127         board[fromY][fromX] = EmptySquare;
9128     }
9129   }
9130
9131     if (gameInfo.holdingsWidth != 0) {
9132
9133       /* !!A lot more code needs to be written to support holdings  */
9134       /* [HGM] OK, so I have written it. Holdings are stored in the */
9135       /* penultimate board files, so they are automaticlly stored   */
9136       /* in the game history.                                       */
9137       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9138                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9139         /* Delete from holdings, by decreasing count */
9140         /* and erasing image if necessary            */
9141         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9142         if(p < (int) BlackPawn) { /* white drop */
9143              p -= (int)WhitePawn;
9144                  p = PieceToNumber((ChessSquare)p);
9145              if(p >= gameInfo.holdingsSize) p = 0;
9146              if(--board[p][BOARD_WIDTH-2] <= 0)
9147                   board[p][BOARD_WIDTH-1] = EmptySquare;
9148              if((int)board[p][BOARD_WIDTH-2] < 0)
9149                         board[p][BOARD_WIDTH-2] = 0;
9150         } else {                  /* black drop */
9151              p -= (int)BlackPawn;
9152                  p = PieceToNumber((ChessSquare)p);
9153              if(p >= gameInfo.holdingsSize) p = 0;
9154              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9155                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9156              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9157                         board[BOARD_HEIGHT-1-p][1] = 0;
9158         }
9159       }
9160       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9161           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9162         /* [HGM] holdings: Add to holdings, if holdings exist */
9163         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9164                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9165                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9166         }
9167         p = (int) captured;
9168         if (p >= (int) BlackPawn) {
9169           p -= (int)BlackPawn;
9170           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9171                   /* in Shogi restore piece to its original  first */
9172                   captured = (ChessSquare) (DEMOTED captured);
9173                   p = DEMOTED p;
9174           }
9175           p = PieceToNumber((ChessSquare)p);
9176           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9177           board[p][BOARD_WIDTH-2]++;
9178           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9179         } else {
9180           p -= (int)WhitePawn;
9181           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9182                   captured = (ChessSquare) (DEMOTED captured);
9183                   p = DEMOTED p;
9184           }
9185           p = PieceToNumber((ChessSquare)p);
9186           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9187           board[BOARD_HEIGHT-1-p][1]++;
9188           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9189         }
9190       }
9191     } else if (gameInfo.variant == VariantAtomic) {
9192       if (captured != EmptySquare) {
9193         int y, x;
9194         for (y = toY-1; y <= toY+1; y++) {
9195           for (x = toX-1; x <= toX+1; x++) {
9196             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9197                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9198               board[y][x] = EmptySquare;
9199             }
9200           }
9201         }
9202         board[toY][toX] = EmptySquare;
9203       }
9204     }
9205     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9206         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9207     } else
9208     if(promoChar == '+') {
9209         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9210         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9211     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9212         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9213     }
9214     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9215                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9216         // [HGM] superchess: take promotion piece out of holdings
9217         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9218         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9219             if(!--board[k][BOARD_WIDTH-2])
9220                 board[k][BOARD_WIDTH-1] = EmptySquare;
9221         } else {
9222             if(!--board[BOARD_HEIGHT-1-k][1])
9223                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9224         }
9225     }
9226
9227 }
9228
9229 /* Updates forwardMostMove */
9230 void
9231 MakeMove(fromX, fromY, toX, toY, promoChar)
9232      int fromX, fromY, toX, toY;
9233      int promoChar;
9234 {
9235 //    forwardMostMove++; // [HGM] bare: moved downstream
9236
9237     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9238         int timeLeft; static int lastLoadFlag=0; int king, piece;
9239         piece = boards[forwardMostMove][fromY][fromX];
9240         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9241         if(gameInfo.variant == VariantKnightmate)
9242             king += (int) WhiteUnicorn - (int) WhiteKing;
9243         if(forwardMostMove == 0) {
9244             if(blackPlaysFirst)
9245                 fprintf(serverMoves, "%s;", second.tidy);
9246             fprintf(serverMoves, "%s;", first.tidy);
9247             if(!blackPlaysFirst)
9248                 fprintf(serverMoves, "%s;", second.tidy);
9249         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9250         lastLoadFlag = loadFlag;
9251         // print base move
9252         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9253         // print castling suffix
9254         if( toY == fromY && piece == king ) {
9255             if(toX-fromX > 1)
9256                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9257             if(fromX-toX >1)
9258                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9259         }
9260         // e.p. suffix
9261         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9262              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9263              boards[forwardMostMove][toY][toX] == EmptySquare
9264              && fromX != toX && fromY != toY)
9265                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9266         // promotion suffix
9267         if(promoChar != NULLCHAR)
9268                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9269         if(!loadFlag) {
9270             fprintf(serverMoves, "/%d/%d",
9271                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9272             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9273             else                      timeLeft = blackTimeRemaining/1000;
9274             fprintf(serverMoves, "/%d", timeLeft);
9275         }
9276         fflush(serverMoves);
9277     }
9278
9279     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9280       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9281                         0, 1);
9282       return;
9283     }
9284     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9285     if (commentList[forwardMostMove+1] != NULL) {
9286         free(commentList[forwardMostMove+1]);
9287         commentList[forwardMostMove+1] = NULL;
9288     }
9289     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9290     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9291     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9292     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9293     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9294     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9295     gameInfo.result = GameUnfinished;
9296     if (gameInfo.resultDetails != NULL) {
9297         free(gameInfo.resultDetails);
9298         gameInfo.resultDetails = NULL;
9299     }
9300     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9301                               moveList[forwardMostMove - 1]);
9302     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9303                              PosFlags(forwardMostMove - 1),
9304                              fromY, fromX, toY, toX, promoChar,
9305                              parseList[forwardMostMove - 1]);
9306     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9307       case MT_NONE:
9308       case MT_STALEMATE:
9309       default:
9310         break;
9311       case MT_CHECK:
9312         if(gameInfo.variant != VariantShogi)
9313             strcat(parseList[forwardMostMove - 1], "+");
9314         break;
9315       case MT_CHECKMATE:
9316       case MT_STAINMATE:
9317         strcat(parseList[forwardMostMove - 1], "#");
9318         break;
9319     }
9320     if (appData.debugMode) {
9321         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9322     }
9323
9324 }
9325
9326 /* Updates currentMove if not pausing */
9327 void
9328 ShowMove(fromX, fromY, toX, toY)
9329 {
9330     int instant = (gameMode == PlayFromGameFile) ?
9331         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9332     if(appData.noGUI) return;
9333     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9334         if (!instant) {
9335             if (forwardMostMove == currentMove + 1) {
9336                 AnimateMove(boards[forwardMostMove - 1],
9337                             fromX, fromY, toX, toY);
9338             }
9339             if (appData.highlightLastMove) {
9340                 SetHighlights(fromX, fromY, toX, toY);
9341             }
9342         }
9343         currentMove = forwardMostMove;
9344     }
9345
9346     if (instant) return;
9347
9348     DisplayMove(currentMove - 1);
9349     DrawPosition(FALSE, boards[currentMove]);
9350     DisplayBothClocks();
9351     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9352     DisplayBook(currentMove);
9353 }
9354
9355 void SendEgtPath(ChessProgramState *cps)
9356 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9357         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9358
9359         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9360
9361         while(*p) {
9362             char c, *q = name+1, *r, *s;
9363
9364             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9365             while(*p && *p != ',') *q++ = *p++;
9366             *q++ = ':'; *q = 0;
9367             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9368                 strcmp(name, ",nalimov:") == 0 ) {
9369                 // take nalimov path from the menu-changeable option first, if it is defined
9370               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9371                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9372             } else
9373             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9374                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9375                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9376                 s = r = StrStr(s, ":") + 1; // beginning of path info
9377                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9378                 c = *r; *r = 0;             // temporarily null-terminate path info
9379                     *--q = 0;               // strip of trailig ':' from name
9380                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9381                 *r = c;
9382                 SendToProgram(buf,cps);     // send egtbpath command for this format
9383             }
9384             if(*p == ',') p++; // read away comma to position for next format name
9385         }
9386 }
9387
9388 void
9389 InitChessProgram(cps, setup)
9390      ChessProgramState *cps;
9391      int setup; /* [HGM] needed to setup FRC opening position */
9392 {
9393     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9394     if (appData.noChessProgram) return;
9395     hintRequested = FALSE;
9396     bookRequested = FALSE;
9397
9398     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9399     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9400     if(cps->memSize) { /* [HGM] memory */
9401       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9402         SendToProgram(buf, cps);
9403     }
9404     SendEgtPath(cps); /* [HGM] EGT */
9405     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9406       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9407         SendToProgram(buf, cps);
9408     }
9409
9410     SendToProgram(cps->initString, cps);
9411     if (gameInfo.variant != VariantNormal &&
9412         gameInfo.variant != VariantLoadable
9413         /* [HGM] also send variant if board size non-standard */
9414         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9415                                             ) {
9416       char *v = VariantName(gameInfo.variant);
9417       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9418         /* [HGM] in protocol 1 we have to assume all variants valid */
9419         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9420         DisplayFatalError(buf, 0, 1);
9421         return;
9422       }
9423
9424       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9425       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9426       if( gameInfo.variant == VariantXiangqi )
9427            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9428       if( gameInfo.variant == VariantShogi )
9429            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9430       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9431            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9432       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9433           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9434            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9435       if( gameInfo.variant == VariantCourier )
9436            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9437       if( gameInfo.variant == VariantSuper )
9438            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9439       if( gameInfo.variant == VariantGreat )
9440            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9441       if( gameInfo.variant == VariantSChess )
9442            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9443
9444       if(overruled) {
9445         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9446                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9447            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9448            if(StrStr(cps->variants, b) == NULL) {
9449                // specific sized variant not known, check if general sizing allowed
9450                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9451                    if(StrStr(cps->variants, "boardsize") == NULL) {
9452                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9453                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9454                        DisplayFatalError(buf, 0, 1);
9455                        return;
9456                    }
9457                    /* [HGM] here we really should compare with the maximum supported board size */
9458                }
9459            }
9460       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9461       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9462       SendToProgram(buf, cps);
9463     }
9464     currentlyInitializedVariant = gameInfo.variant;
9465
9466     /* [HGM] send opening position in FRC to first engine */
9467     if(setup) {
9468           SendToProgram("force\n", cps);
9469           SendBoard(cps, 0);
9470           /* engine is now in force mode! Set flag to wake it up after first move. */
9471           setboardSpoiledMachineBlack = 1;
9472     }
9473
9474     if (cps->sendICS) {
9475       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9476       SendToProgram(buf, cps);
9477     }
9478     cps->maybeThinking = FALSE;
9479     cps->offeredDraw = 0;
9480     if (!appData.icsActive) {
9481         SendTimeControl(cps, movesPerSession, timeControl,
9482                         timeIncrement, appData.searchDepth,
9483                         searchTime);
9484     }
9485     if (appData.showThinking
9486         // [HGM] thinking: four options require thinking output to be sent
9487         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9488                                 ) {
9489         SendToProgram("post\n", cps);
9490     }
9491     SendToProgram("hard\n", cps);
9492     if (!appData.ponderNextMove) {
9493         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9494            it without being sure what state we are in first.  "hard"
9495            is not a toggle, so that one is OK.
9496          */
9497         SendToProgram("easy\n", cps);
9498     }
9499     if (cps->usePing) {
9500       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9501       SendToProgram(buf, cps);
9502     }
9503     cps->initDone = TRUE;
9504     ClearEngineOutputPane(cps == &second);
9505 }
9506
9507
9508 void
9509 StartChessProgram(cps)
9510      ChessProgramState *cps;
9511 {
9512     char buf[MSG_SIZ];
9513     int err;
9514
9515     if (appData.noChessProgram) return;
9516     cps->initDone = FALSE;
9517
9518     if (strcmp(cps->host, "localhost") == 0) {
9519         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9520     } else if (*appData.remoteShell == NULLCHAR) {
9521         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9522     } else {
9523         if (*appData.remoteUser == NULLCHAR) {
9524           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9525                     cps->program);
9526         } else {
9527           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9528                     cps->host, appData.remoteUser, cps->program);
9529         }
9530         err = StartChildProcess(buf, "", &cps->pr);
9531     }
9532
9533     if (err != 0) {
9534       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9535         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9536         if(cps != &first) return;
9537         appData.noChessProgram = TRUE;
9538         ThawUI();
9539         SetNCPMode();
9540 //      DisplayFatalError(buf, err, 1);
9541 //      cps->pr = NoProc;
9542 //      cps->isr = NULL;
9543         return;
9544     }
9545
9546     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9547     if (cps->protocolVersion > 1) {
9548       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9549       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9550       cps->comboCnt = 0;  //                and values of combo boxes
9551       SendToProgram(buf, cps);
9552     } else {
9553       SendToProgram("xboard\n", cps);
9554     }
9555 }
9556
9557 void
9558 TwoMachinesEventIfReady P((void))
9559 {
9560   static int curMess = 0;
9561   if (first.lastPing != first.lastPong) {
9562     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9563     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9564     return;
9565   }
9566   if (second.lastPing != second.lastPong) {
9567     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9568     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9569     return;
9570   }
9571   DisplayMessage("", ""); curMess = 0;
9572   ThawUI();
9573   TwoMachinesEvent();
9574 }
9575
9576 char *
9577 MakeName(char *template)
9578 {
9579     time_t clock;
9580     struct tm *tm;
9581     static char buf[MSG_SIZ];
9582     char *p = buf;
9583     int i;
9584
9585     clock = time((time_t *)NULL);
9586     tm = localtime(&clock);
9587
9588     while(*p++ = *template++) if(p[-1] == '%') {
9589         switch(*template++) {
9590           case 0:   *p = 0; return buf;
9591           case 'Y': i = tm->tm_year+1900; break;
9592           case 'y': i = tm->tm_year-100; break;
9593           case 'M': i = tm->tm_mon+1; break;
9594           case 'd': i = tm->tm_mday; break;
9595           case 'h': i = tm->tm_hour; break;
9596           case 'm': i = tm->tm_min; break;
9597           case 's': i = tm->tm_sec; break;
9598           default:  i = 0;
9599         }
9600         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9601     }
9602     return buf;
9603 }
9604
9605 int
9606 CountPlayers(char *p)
9607 {
9608     int n = 0;
9609     while(p = strchr(p, '\n')) p++, n++; // count participants
9610     return n;
9611 }
9612
9613 FILE *
9614 WriteTourneyFile(char *results)
9615 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9616     FILE *f = fopen(appData.tourneyFile, "w");
9617     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9618         // create a file with tournament description
9619         fprintf(f, "-participants {%s}\n", appData.participants);
9620         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9621         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9622         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9623         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9624         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9625         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9626         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9627         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9628         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9629         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9630         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9631         if(searchTime > 0)
9632                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9633         else {
9634                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9635                 fprintf(f, "-tc %s\n", appData.timeControl);
9636                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9637         }
9638         fprintf(f, "-results \"%s\"\n", results);
9639     }
9640     return f;
9641 }
9642
9643 int
9644 CreateTourney(char *name)
9645 {
9646         FILE *f;
9647         if(name[0] == NULLCHAR) {
9648             if(appData.participants[0])
9649                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9650             return 0;
9651         }
9652         f = fopen(name, "r");
9653         if(f) { // file exists
9654             ASSIGN(appData.tourneyFile, name);
9655             ParseArgsFromFile(f); // parse it
9656         } else {
9657             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9658             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9659                 DisplayError(_("Not enough participants"), 0);
9660                 return 0;
9661             }
9662             ASSIGN(appData.tourneyFile, name);
9663             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9664             if((f = WriteTourneyFile("")) == NULL) return 0;
9665         }
9666         fclose(f);
9667         appData.noChessProgram = FALSE;
9668         appData.clockMode = TRUE;
9669         SetGNUMode();
9670         return 1;
9671 }
9672
9673 #define MAXENGINES 1000
9674 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9675
9676 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9677 {
9678     char buf[MSG_SIZ], *p, *q;
9679     int i=1;
9680     while(*names) {
9681         p = names; q = buf;
9682         while(*p && *p != '\n') *q++ = *p++;
9683         *q = 0;
9684         if(engineList[i]) free(engineList[i]);
9685         engineList[i] = strdup(buf);
9686         if(*p == '\n') p++;
9687         TidyProgramName(engineList[i], "localhost", buf);
9688         if(engineMnemonic[i]) free(engineMnemonic[i]);
9689         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9690             strcat(buf, " (");
9691             sscanf(q + 8, "%s", buf + strlen(buf));
9692             strcat(buf, ")");
9693         }
9694         engineMnemonic[i] = strdup(buf);
9695         names = p; i++;
9696       if(i > MAXENGINES - 2) break;
9697     }
9698     engineList[i] = NULL;
9699 }
9700
9701 // following implemented as macro to avoid type limitations
9702 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9703
9704 void SwapEngines(int n)
9705 {   // swap settings for first engine and other engine (so far only some selected options)
9706     int h;
9707     char *p;
9708     if(n == 0) return;
9709     SWAP(directory, p)
9710     SWAP(chessProgram, p)
9711     SWAP(isUCI, h)
9712     SWAP(hasOwnBookUCI, h)
9713     SWAP(protocolVersion, h)
9714     SWAP(reuse, h)
9715     SWAP(scoreIsAbsolute, h)
9716     SWAP(timeOdds, h)
9717     SWAP(logo, p)
9718     SWAP(pgnName, p)
9719 }
9720
9721 void
9722 SetPlayer(int player)
9723 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9724     int i;
9725     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9726     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9727     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9728     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9729     if(mnemonic[i]) {
9730         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9731         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9732         ParseArgsFromString(buf);
9733     }
9734     free(engineName);
9735 }
9736
9737 int
9738 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9739 {   // determine players from game number
9740     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9741
9742     if(appData.tourneyType == 0) {
9743         roundsPerCycle = (nPlayers - 1) | 1;
9744         pairingsPerRound = nPlayers / 2;
9745     } else if(appData.tourneyType > 0) {
9746         roundsPerCycle = nPlayers - appData.tourneyType;
9747         pairingsPerRound = appData.tourneyType;
9748     }
9749     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9750     gamesPerCycle = gamesPerRound * roundsPerCycle;
9751     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9752     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9753     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9754     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9755     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9756     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9757
9758     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9759     if(appData.roundSync) *syncInterval = gamesPerRound;
9760
9761     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9762
9763     if(appData.tourneyType == 0) {
9764         if(curPairing == (nPlayers-1)/2 ) {
9765             *whitePlayer = curRound;
9766             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9767         } else {
9768             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9769             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9770             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9771             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9772         }
9773     } else if(appData.tourneyType > 0) {
9774         *whitePlayer = curPairing;
9775         *blackPlayer = curRound + appData.tourneyType;
9776     }
9777
9778     // take care of white/black alternation per round. 
9779     // For cycles and games this is already taken care of by default, derived from matchGame!
9780     return curRound & 1;
9781 }
9782
9783 int
9784 NextTourneyGame(int nr, int *swapColors)
9785 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9786     char *p, *q;
9787     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9788     FILE *tf;
9789     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9790     tf = fopen(appData.tourneyFile, "r");
9791     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9792     ParseArgsFromFile(tf); fclose(tf);
9793     InitTimeControls(); // TC might be altered from tourney file
9794
9795     nPlayers = CountPlayers(appData.participants); // count participants
9796     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9797     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9798
9799     if(syncInterval) {
9800         p = q = appData.results;
9801         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9802         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9803             DisplayMessage(_("Waiting for other game(s)"),"");
9804             waitingForGame = TRUE;
9805             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9806             return 0;
9807         }
9808         waitingForGame = FALSE;
9809     }
9810
9811     if(appData.tourneyType < 0) {
9812         if(nr>=0 && !pairingReceived) {
9813             char buf[1<<16];
9814             if(pairing.pr == NoProc) {
9815                 if(!appData.pairingEngine[0]) {
9816                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9817                     return 0;
9818                 }
9819                 StartChessProgram(&pairing); // starts the pairing engine
9820             }
9821             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9822             SendToProgram(buf, &pairing);
9823             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9824             SendToProgram(buf, &pairing);
9825             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9826         }
9827         pairingReceived = 0;                              // ... so we continue here 
9828         *swapColors = 0;
9829         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9830         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9831         matchGame = 1; roundNr = nr / syncInterval + 1;
9832     }
9833
9834     if(first.pr != NoProc) return 1; // engines already loaded
9835
9836     // redefine engines, engine dir, etc.
9837     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9838     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9839     SwapEngines(1);
9840     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9841     SwapEngines(1);         // and make that valid for second engine by swapping
9842     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9843     InitEngine(&second, 1);
9844     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9845     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9846     return 1;
9847 }
9848
9849 void
9850 NextMatchGame()
9851 {   // performs game initialization that does not invoke engines, and then tries to start the game
9852     int firstWhite, swapColors = 0;
9853     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9854     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9855     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9856     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9857     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9858     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9859     Reset(FALSE, first.pr != NoProc);
9860     appData.noChessProgram = FALSE;
9861     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9862     TwoMachinesEvent();
9863 }
9864
9865 void UserAdjudicationEvent( int result )
9866 {
9867     ChessMove gameResult = GameIsDrawn;
9868
9869     if( result > 0 ) {
9870         gameResult = WhiteWins;
9871     }
9872     else if( result < 0 ) {
9873         gameResult = BlackWins;
9874     }
9875
9876     if( gameMode == TwoMachinesPlay ) {
9877         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9878     }
9879 }
9880
9881
9882 // [HGM] save: calculate checksum of game to make games easily identifiable
9883 int StringCheckSum(char *s)
9884 {
9885         int i = 0;
9886         if(s==NULL) return 0;
9887         while(*s) i = i*259 + *s++;
9888         return i;
9889 }
9890
9891 int GameCheckSum()
9892 {
9893         int i, sum=0;
9894         for(i=backwardMostMove; i<forwardMostMove; i++) {
9895                 sum += pvInfoList[i].depth;
9896                 sum += StringCheckSum(parseList[i]);
9897                 sum += StringCheckSum(commentList[i]);
9898                 sum *= 261;
9899         }
9900         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9901         return sum + StringCheckSum(commentList[i]);
9902 } // end of save patch
9903
9904 void
9905 GameEnds(result, resultDetails, whosays)
9906      ChessMove result;
9907      char *resultDetails;
9908      int whosays;
9909 {
9910     GameMode nextGameMode;
9911     int isIcsGame;
9912     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9913
9914     if(endingGame) return; /* [HGM] crash: forbid recursion */
9915     endingGame = 1;
9916     if(twoBoards) { // [HGM] dual: switch back to one board
9917         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9918         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9919     }
9920     if (appData.debugMode) {
9921       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9922               result, resultDetails ? resultDetails : "(null)", whosays);
9923     }
9924
9925     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9926
9927     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9928         /* If we are playing on ICS, the server decides when the
9929            game is over, but the engine can offer to draw, claim
9930            a draw, or resign.
9931          */
9932 #if ZIPPY
9933         if (appData.zippyPlay && first.initDone) {
9934             if (result == GameIsDrawn) {
9935                 /* In case draw still needs to be claimed */
9936                 SendToICS(ics_prefix);
9937                 SendToICS("draw\n");
9938             } else if (StrCaseStr(resultDetails, "resign")) {
9939                 SendToICS(ics_prefix);
9940                 SendToICS("resign\n");
9941             }
9942         }
9943 #endif
9944         endingGame = 0; /* [HGM] crash */
9945         return;
9946     }
9947
9948     /* If we're loading the game from a file, stop */
9949     if (whosays == GE_FILE) {
9950       (void) StopLoadGameTimer();
9951       gameFileFP = NULL;
9952     }
9953
9954     /* Cancel draw offers */
9955     first.offeredDraw = second.offeredDraw = 0;
9956
9957     /* If this is an ICS game, only ICS can really say it's done;
9958        if not, anyone can. */
9959     isIcsGame = (gameMode == IcsPlayingWhite ||
9960                  gameMode == IcsPlayingBlack ||
9961                  gameMode == IcsObserving    ||
9962                  gameMode == IcsExamining);
9963
9964     if (!isIcsGame || whosays == GE_ICS) {
9965         /* OK -- not an ICS game, or ICS said it was done */
9966         StopClocks();
9967         if (!isIcsGame && !appData.noChessProgram)
9968           SetUserThinkingEnables();
9969
9970         /* [HGM] if a machine claims the game end we verify this claim */
9971         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9972             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9973                 char claimer;
9974                 ChessMove trueResult = (ChessMove) -1;
9975
9976                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9977                                             first.twoMachinesColor[0] :
9978                                             second.twoMachinesColor[0] ;
9979
9980                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9981                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9982                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9983                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9984                 } else
9985                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9986                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9987                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9988                 } else
9989                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9990                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9991                 }
9992
9993                 // now verify win claims, but not in drop games, as we don't understand those yet
9994                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9995                                                  || gameInfo.variant == VariantGreat) &&
9996                     (result == WhiteWins && claimer == 'w' ||
9997                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9998                       if (appData.debugMode) {
9999                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10000                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10001                       }
10002                       if(result != trueResult) {
10003                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10004                               result = claimer == 'w' ? BlackWins : WhiteWins;
10005                               resultDetails = buf;
10006                       }
10007                 } else
10008                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10009                     && (forwardMostMove <= backwardMostMove ||
10010                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10011                         (claimer=='b')==(forwardMostMove&1))
10012                                                                                   ) {
10013                       /* [HGM] verify: draws that were not flagged are false claims */
10014                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10015                       result = claimer == 'w' ? BlackWins : WhiteWins;
10016                       resultDetails = buf;
10017                 }
10018                 /* (Claiming a loss is accepted no questions asked!) */
10019             }
10020             /* [HGM] bare: don't allow bare King to win */
10021             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10022                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10023                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10024                && result != GameIsDrawn)
10025             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10026                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10027                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10028                         if(p >= 0 && p <= (int)WhiteKing) k++;
10029                 }
10030                 if (appData.debugMode) {
10031                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10032                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10033                 }
10034                 if(k <= 1) {
10035                         result = GameIsDrawn;
10036                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10037                         resultDetails = buf;
10038                 }
10039             }
10040         }
10041
10042
10043         if(serverMoves != NULL && !loadFlag) { char c = '=';
10044             if(result==WhiteWins) c = '+';
10045             if(result==BlackWins) c = '-';
10046             if(resultDetails != NULL)
10047                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10048         }
10049         if (resultDetails != NULL) {
10050             gameInfo.result = result;
10051             gameInfo.resultDetails = StrSave(resultDetails);
10052
10053             /* display last move only if game was not loaded from file */
10054             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10055                 DisplayMove(currentMove - 1);
10056
10057             if (forwardMostMove != 0) {
10058                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10059                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10060                                                                 ) {
10061                     if (*appData.saveGameFile != NULLCHAR) {
10062                         SaveGameToFile(appData.saveGameFile, TRUE);
10063                     } else if (appData.autoSaveGames) {
10064                         AutoSaveGame();
10065                     }
10066                     if (*appData.savePositionFile != NULLCHAR) {
10067                         SavePositionToFile(appData.savePositionFile);
10068                     }
10069                 }
10070             }
10071
10072             /* Tell program how game ended in case it is learning */
10073             /* [HGM] Moved this to after saving the PGN, just in case */
10074             /* engine died and we got here through time loss. In that */
10075             /* case we will get a fatal error writing the pipe, which */
10076             /* would otherwise lose us the PGN.                       */
10077             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10078             /* output during GameEnds should never be fatal anymore   */
10079             if (gameMode == MachinePlaysWhite ||
10080                 gameMode == MachinePlaysBlack ||
10081                 gameMode == TwoMachinesPlay ||
10082                 gameMode == IcsPlayingWhite ||
10083                 gameMode == IcsPlayingBlack ||
10084                 gameMode == BeginningOfGame) {
10085                 char buf[MSG_SIZ];
10086                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10087                         resultDetails);
10088                 if (first.pr != NoProc) {
10089                     SendToProgram(buf, &first);
10090                 }
10091                 if (second.pr != NoProc &&
10092                     gameMode == TwoMachinesPlay) {
10093                     SendToProgram(buf, &second);
10094                 }
10095             }
10096         }
10097
10098         if (appData.icsActive) {
10099             if (appData.quietPlay &&
10100                 (gameMode == IcsPlayingWhite ||
10101                  gameMode == IcsPlayingBlack)) {
10102                 SendToICS(ics_prefix);
10103                 SendToICS("set shout 1\n");
10104             }
10105             nextGameMode = IcsIdle;
10106             ics_user_moved = FALSE;
10107             /* clean up premove.  It's ugly when the game has ended and the
10108              * premove highlights are still on the board.
10109              */
10110             if (gotPremove) {
10111               gotPremove = FALSE;
10112               ClearPremoveHighlights();
10113               DrawPosition(FALSE, boards[currentMove]);
10114             }
10115             if (whosays == GE_ICS) {
10116                 switch (result) {
10117                 case WhiteWins:
10118                     if (gameMode == IcsPlayingWhite)
10119                         PlayIcsWinSound();
10120                     else if(gameMode == IcsPlayingBlack)
10121                         PlayIcsLossSound();
10122                     break;
10123                 case BlackWins:
10124                     if (gameMode == IcsPlayingBlack)
10125                         PlayIcsWinSound();
10126                     else if(gameMode == IcsPlayingWhite)
10127                         PlayIcsLossSound();
10128                     break;
10129                 case GameIsDrawn:
10130                     PlayIcsDrawSound();
10131                     break;
10132                 default:
10133                     PlayIcsUnfinishedSound();
10134                 }
10135             }
10136         } else if (gameMode == EditGame ||
10137                    gameMode == PlayFromGameFile ||
10138                    gameMode == AnalyzeMode ||
10139                    gameMode == AnalyzeFile) {
10140             nextGameMode = gameMode;
10141         } else {
10142             nextGameMode = EndOfGame;
10143         }
10144         pausing = FALSE;
10145         ModeHighlight();
10146     } else {
10147         nextGameMode = gameMode;
10148     }
10149
10150     if (appData.noChessProgram) {
10151         gameMode = nextGameMode;
10152         ModeHighlight();
10153         endingGame = 0; /* [HGM] crash */
10154         return;
10155     }
10156
10157     if (first.reuse) {
10158         /* Put first chess program into idle state */
10159         if (first.pr != NoProc &&
10160             (gameMode == MachinePlaysWhite ||
10161              gameMode == MachinePlaysBlack ||
10162              gameMode == TwoMachinesPlay ||
10163              gameMode == IcsPlayingWhite ||
10164              gameMode == IcsPlayingBlack ||
10165              gameMode == BeginningOfGame)) {
10166             SendToProgram("force\n", &first);
10167             if (first.usePing) {
10168               char buf[MSG_SIZ];
10169               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10170               SendToProgram(buf, &first);
10171             }
10172         }
10173     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10174         /* Kill off first chess program */
10175         if (first.isr != NULL)
10176           RemoveInputSource(first.isr);
10177         first.isr = NULL;
10178
10179         if (first.pr != NoProc) {
10180             ExitAnalyzeMode();
10181             DoSleep( appData.delayBeforeQuit );
10182             SendToProgram("quit\n", &first);
10183             DoSleep( appData.delayAfterQuit );
10184             DestroyChildProcess(first.pr, first.useSigterm);
10185         }
10186         first.pr = NoProc;
10187     }
10188     if (second.reuse) {
10189         /* Put second chess program into idle state */
10190         if (second.pr != NoProc &&
10191             gameMode == TwoMachinesPlay) {
10192             SendToProgram("force\n", &second);
10193             if (second.usePing) {
10194               char buf[MSG_SIZ];
10195               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10196               SendToProgram(buf, &second);
10197             }
10198         }
10199     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10200         /* Kill off second chess program */
10201         if (second.isr != NULL)
10202           RemoveInputSource(second.isr);
10203         second.isr = NULL;
10204
10205         if (second.pr != NoProc) {
10206             DoSleep( appData.delayBeforeQuit );
10207             SendToProgram("quit\n", &second);
10208             DoSleep( appData.delayAfterQuit );
10209             DestroyChildProcess(second.pr, second.useSigterm);
10210         }
10211         second.pr = NoProc;
10212     }
10213
10214     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10215         char resChar = '=';
10216         switch (result) {
10217         case WhiteWins:
10218           resChar = '+';
10219           if (first.twoMachinesColor[0] == 'w') {
10220             first.matchWins++;
10221           } else {
10222             second.matchWins++;
10223           }
10224           break;
10225         case BlackWins:
10226           resChar = '-';
10227           if (first.twoMachinesColor[0] == 'b') {
10228             first.matchWins++;
10229           } else {
10230             second.matchWins++;
10231           }
10232           break;
10233         case GameUnfinished:
10234           resChar = ' ';
10235         default:
10236           break;
10237         }
10238
10239         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10240         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10241             ReserveGame(nextGame, resChar); // sets nextGame
10242             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10243             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10244         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10245
10246         if (nextGame <= appData.matchGames && !abortMatch) {
10247             gameMode = nextGameMode;
10248             matchGame = nextGame; // this will be overruled in tourney mode!
10249             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10250             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10251             endingGame = 0; /* [HGM] crash */
10252             return;
10253         } else {
10254             gameMode = nextGameMode;
10255             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10256                      first.tidy, second.tidy,
10257                      first.matchWins, second.matchWins,
10258                      appData.matchGames - (first.matchWins + second.matchWins));
10259             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10260             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10261             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10262                 first.twoMachinesColor = "black\n";
10263                 second.twoMachinesColor = "white\n";
10264             } else {
10265                 first.twoMachinesColor = "white\n";
10266                 second.twoMachinesColor = "black\n";
10267             }
10268         }
10269     }
10270     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10271         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10272       ExitAnalyzeMode();
10273     gameMode = nextGameMode;
10274     ModeHighlight();
10275     endingGame = 0;  /* [HGM] crash */
10276     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10277         if(matchMode == TRUE) { // match through command line: exit with or without popup
10278             if(ranking) {
10279                 ToNrEvent(forwardMostMove);
10280                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10281                 else ExitEvent(0);
10282             } else DisplayFatalError(buf, 0, 0);
10283         } else { // match through menu; just stop, with or without popup
10284             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10285             ModeHighlight();
10286             if(ranking){
10287                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10288             } else DisplayNote(buf);
10289       }
10290       if(ranking) free(ranking);
10291     }
10292 }
10293
10294 /* Assumes program was just initialized (initString sent).
10295    Leaves program in force mode. */
10296 void
10297 FeedMovesToProgram(cps, upto)
10298      ChessProgramState *cps;
10299      int upto;
10300 {
10301     int i;
10302
10303     if (appData.debugMode)
10304       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10305               startedFromSetupPosition ? "position and " : "",
10306               backwardMostMove, upto, cps->which);
10307     if(currentlyInitializedVariant != gameInfo.variant) {
10308       char buf[MSG_SIZ];
10309         // [HGM] variantswitch: make engine aware of new variant
10310         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10311                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10312         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10313         SendToProgram(buf, cps);
10314         currentlyInitializedVariant = gameInfo.variant;
10315     }
10316     SendToProgram("force\n", cps);
10317     if (startedFromSetupPosition) {
10318         SendBoard(cps, backwardMostMove);
10319     if (appData.debugMode) {
10320         fprintf(debugFP, "feedMoves\n");
10321     }
10322     }
10323     for (i = backwardMostMove; i < upto; i++) {
10324         SendMoveToProgram(i, cps);
10325     }
10326 }
10327
10328
10329 int
10330 ResurrectChessProgram()
10331 {
10332      /* The chess program may have exited.
10333         If so, restart it and feed it all the moves made so far. */
10334     static int doInit = 0;
10335
10336     if (appData.noChessProgram) return 1;
10337
10338     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10339         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10340         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10341         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10342     } else {
10343         if (first.pr != NoProc) return 1;
10344         StartChessProgram(&first);
10345     }
10346     InitChessProgram(&first, FALSE);
10347     FeedMovesToProgram(&first, currentMove);
10348
10349     if (!first.sendTime) {
10350         /* can't tell gnuchess what its clock should read,
10351            so we bow to its notion. */
10352         ResetClocks();
10353         timeRemaining[0][currentMove] = whiteTimeRemaining;
10354         timeRemaining[1][currentMove] = blackTimeRemaining;
10355     }
10356
10357     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10358                 appData.icsEngineAnalyze) && first.analysisSupport) {
10359       SendToProgram("analyze\n", &first);
10360       first.analyzing = TRUE;
10361     }
10362     return 1;
10363 }
10364
10365 /*
10366  * Button procedures
10367  */
10368 void
10369 Reset(redraw, init)
10370      int redraw, init;
10371 {
10372     int i;
10373
10374     if (appData.debugMode) {
10375         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10376                 redraw, init, gameMode);
10377     }
10378     CleanupTail(); // [HGM] vari: delete any stored variations
10379     pausing = pauseExamInvalid = FALSE;
10380     startedFromSetupPosition = blackPlaysFirst = FALSE;
10381     firstMove = TRUE;
10382     whiteFlag = blackFlag = FALSE;
10383     userOfferedDraw = FALSE;
10384     hintRequested = bookRequested = FALSE;
10385     first.maybeThinking = FALSE;
10386     second.maybeThinking = FALSE;
10387     first.bookSuspend = FALSE; // [HGM] book
10388     second.bookSuspend = FALSE;
10389     thinkOutput[0] = NULLCHAR;
10390     lastHint[0] = NULLCHAR;
10391     ClearGameInfo(&gameInfo);
10392     gameInfo.variant = StringToVariant(appData.variant);
10393     ics_user_moved = ics_clock_paused = FALSE;
10394     ics_getting_history = H_FALSE;
10395     ics_gamenum = -1;
10396     white_holding[0] = black_holding[0] = NULLCHAR;
10397     ClearProgramStats();
10398     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10399
10400     ResetFrontEnd();
10401     ClearHighlights();
10402     flipView = appData.flipView;
10403     ClearPremoveHighlights();
10404     gotPremove = FALSE;
10405     alarmSounded = FALSE;
10406
10407     GameEnds(EndOfFile, NULL, GE_PLAYER);
10408     if(appData.serverMovesName != NULL) {
10409         /* [HGM] prepare to make moves file for broadcasting */
10410         clock_t t = clock();
10411         if(serverMoves != NULL) fclose(serverMoves);
10412         serverMoves = fopen(appData.serverMovesName, "r");
10413         if(serverMoves != NULL) {
10414             fclose(serverMoves);
10415             /* delay 15 sec before overwriting, so all clients can see end */
10416             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10417         }
10418         serverMoves = fopen(appData.serverMovesName, "w");
10419     }
10420
10421     ExitAnalyzeMode();
10422     gameMode = BeginningOfGame;
10423     ModeHighlight();
10424     if(appData.icsActive) gameInfo.variant = VariantNormal;
10425     currentMove = forwardMostMove = backwardMostMove = 0;
10426     InitPosition(redraw);
10427     for (i = 0; i < MAX_MOVES; i++) {
10428         if (commentList[i] != NULL) {
10429             free(commentList[i]);
10430             commentList[i] = NULL;
10431         }
10432     }
10433     ResetClocks();
10434     timeRemaining[0][0] = whiteTimeRemaining;
10435     timeRemaining[1][0] = blackTimeRemaining;
10436
10437     if (first.pr == NULL) {
10438         StartChessProgram(&first);
10439     }
10440     if (init) {
10441             InitChessProgram(&first, startedFromSetupPosition);
10442     }
10443     DisplayTitle("");
10444     DisplayMessage("", "");
10445     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10446     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10447 }
10448
10449 void
10450 AutoPlayGameLoop()
10451 {
10452     for (;;) {
10453         if (!AutoPlayOneMove())
10454           return;
10455         if (matchMode || appData.timeDelay == 0)
10456           continue;
10457         if (appData.timeDelay < 0)
10458           return;
10459         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10460         break;
10461     }
10462 }
10463
10464
10465 int
10466 AutoPlayOneMove()
10467 {
10468     int fromX, fromY, toX, toY;
10469
10470     if (appData.debugMode) {
10471       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10472     }
10473
10474     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10475       return FALSE;
10476
10477     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10478       pvInfoList[currentMove].depth = programStats.depth;
10479       pvInfoList[currentMove].score = programStats.score;
10480       pvInfoList[currentMove].time  = 0;
10481       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10482     }
10483
10484     if (currentMove >= forwardMostMove) {
10485       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10486       gameMode = EditGame;
10487       ModeHighlight();
10488
10489       /* [AS] Clear current move marker at the end of a game */
10490       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10491
10492       return FALSE;
10493     }
10494
10495     toX = moveList[currentMove][2] - AAA;
10496     toY = moveList[currentMove][3] - ONE;
10497
10498     if (moveList[currentMove][1] == '@') {
10499         if (appData.highlightLastMove) {
10500             SetHighlights(-1, -1, toX, toY);
10501         }
10502     } else {
10503         fromX = moveList[currentMove][0] - AAA;
10504         fromY = moveList[currentMove][1] - ONE;
10505
10506         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10507
10508         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10509
10510         if (appData.highlightLastMove) {
10511             SetHighlights(fromX, fromY, toX, toY);
10512         }
10513     }
10514     DisplayMove(currentMove);
10515     SendMoveToProgram(currentMove++, &first);
10516     DisplayBothClocks();
10517     DrawPosition(FALSE, boards[currentMove]);
10518     // [HGM] PV info: always display, routine tests if empty
10519     DisplayComment(currentMove - 1, commentList[currentMove]);
10520     return TRUE;
10521 }
10522
10523
10524 int
10525 LoadGameOneMove(readAhead)
10526      ChessMove readAhead;
10527 {
10528     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10529     char promoChar = NULLCHAR;
10530     ChessMove moveType;
10531     char move[MSG_SIZ];
10532     char *p, *q;
10533
10534     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10535         gameMode != AnalyzeMode && gameMode != Training) {
10536         gameFileFP = NULL;
10537         return FALSE;
10538     }
10539
10540     yyboardindex = forwardMostMove;
10541     if (readAhead != EndOfFile) {
10542       moveType = readAhead;
10543     } else {
10544       if (gameFileFP == NULL)
10545           return FALSE;
10546       moveType = (ChessMove) Myylex();
10547     }
10548
10549     done = FALSE;
10550     switch (moveType) {
10551       case Comment:
10552         if (appData.debugMode)
10553           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10554         p = yy_text;
10555
10556         /* append the comment but don't display it */
10557         AppendComment(currentMove, p, FALSE);
10558         return TRUE;
10559
10560       case WhiteCapturesEnPassant:
10561       case BlackCapturesEnPassant:
10562       case WhitePromotion:
10563       case BlackPromotion:
10564       case WhiteNonPromotion:
10565       case BlackNonPromotion:
10566       case NormalMove:
10567       case WhiteKingSideCastle:
10568       case WhiteQueenSideCastle:
10569       case BlackKingSideCastle:
10570       case BlackQueenSideCastle:
10571       case WhiteKingSideCastleWild:
10572       case WhiteQueenSideCastleWild:
10573       case BlackKingSideCastleWild:
10574       case BlackQueenSideCastleWild:
10575       /* PUSH Fabien */
10576       case WhiteHSideCastleFR:
10577       case WhiteASideCastleFR:
10578       case BlackHSideCastleFR:
10579       case BlackASideCastleFR:
10580       /* POP Fabien */
10581         if (appData.debugMode)
10582           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10583         fromX = currentMoveString[0] - AAA;
10584         fromY = currentMoveString[1] - ONE;
10585         toX = currentMoveString[2] - AAA;
10586         toY = currentMoveString[3] - ONE;
10587         promoChar = currentMoveString[4];
10588         break;
10589
10590       case WhiteDrop:
10591       case BlackDrop:
10592         if (appData.debugMode)
10593           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10594         fromX = moveType == WhiteDrop ?
10595           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10596         (int) CharToPiece(ToLower(currentMoveString[0]));
10597         fromY = DROP_RANK;
10598         toX = currentMoveString[2] - AAA;
10599         toY = currentMoveString[3] - ONE;
10600         break;
10601
10602       case WhiteWins:
10603       case BlackWins:
10604       case GameIsDrawn:
10605       case GameUnfinished:
10606         if (appData.debugMode)
10607           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10608         p = strchr(yy_text, '{');
10609         if (p == NULL) p = strchr(yy_text, '(');
10610         if (p == NULL) {
10611             p = yy_text;
10612             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10613         } else {
10614             q = strchr(p, *p == '{' ? '}' : ')');
10615             if (q != NULL) *q = NULLCHAR;
10616             p++;
10617         }
10618         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10619         GameEnds(moveType, p, GE_FILE);
10620         done = TRUE;
10621         if (cmailMsgLoaded) {
10622             ClearHighlights();
10623             flipView = WhiteOnMove(currentMove);
10624             if (moveType == GameUnfinished) flipView = !flipView;
10625             if (appData.debugMode)
10626               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10627         }
10628         break;
10629
10630       case EndOfFile:
10631         if (appData.debugMode)
10632           fprintf(debugFP, "Parser hit end of file\n");
10633         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10634           case MT_NONE:
10635           case MT_CHECK:
10636             break;
10637           case MT_CHECKMATE:
10638           case MT_STAINMATE:
10639             if (WhiteOnMove(currentMove)) {
10640                 GameEnds(BlackWins, "Black mates", GE_FILE);
10641             } else {
10642                 GameEnds(WhiteWins, "White mates", GE_FILE);
10643             }
10644             break;
10645           case MT_STALEMATE:
10646             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10647             break;
10648         }
10649         done = TRUE;
10650         break;
10651
10652       case MoveNumberOne:
10653         if (lastLoadGameStart == GNUChessGame) {
10654             /* GNUChessGames have numbers, but they aren't move numbers */
10655             if (appData.debugMode)
10656               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10657                       yy_text, (int) moveType);
10658             return LoadGameOneMove(EndOfFile); /* tail recursion */
10659         }
10660         /* else fall thru */
10661
10662       case XBoardGame:
10663       case GNUChessGame:
10664       case PGNTag:
10665         /* Reached start of next game in file */
10666         if (appData.debugMode)
10667           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10668         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10669           case MT_NONE:
10670           case MT_CHECK:
10671             break;
10672           case MT_CHECKMATE:
10673           case MT_STAINMATE:
10674             if (WhiteOnMove(currentMove)) {
10675                 GameEnds(BlackWins, "Black mates", GE_FILE);
10676             } else {
10677                 GameEnds(WhiteWins, "White mates", GE_FILE);
10678             }
10679             break;
10680           case MT_STALEMATE:
10681             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10682             break;
10683         }
10684         done = TRUE;
10685         break;
10686
10687       case PositionDiagram:     /* should not happen; ignore */
10688       case ElapsedTime:         /* ignore */
10689       case NAG:                 /* ignore */
10690         if (appData.debugMode)
10691           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10692                   yy_text, (int) moveType);
10693         return LoadGameOneMove(EndOfFile); /* tail recursion */
10694
10695       case IllegalMove:
10696         if (appData.testLegality) {
10697             if (appData.debugMode)
10698               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10699             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10700                     (forwardMostMove / 2) + 1,
10701                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10702             DisplayError(move, 0);
10703             done = TRUE;
10704         } else {
10705             if (appData.debugMode)
10706               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10707                       yy_text, currentMoveString);
10708             fromX = currentMoveString[0] - AAA;
10709             fromY = currentMoveString[1] - ONE;
10710             toX = currentMoveString[2] - AAA;
10711             toY = currentMoveString[3] - ONE;
10712             promoChar = currentMoveString[4];
10713         }
10714         break;
10715
10716       case AmbiguousMove:
10717         if (appData.debugMode)
10718           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10719         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10720                 (forwardMostMove / 2) + 1,
10721                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10722         DisplayError(move, 0);
10723         done = TRUE;
10724         break;
10725
10726       default:
10727       case ImpossibleMove:
10728         if (appData.debugMode)
10729           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10730         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10731                 (forwardMostMove / 2) + 1,
10732                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10733         DisplayError(move, 0);
10734         done = TRUE;
10735         break;
10736     }
10737
10738     if (done) {
10739         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10740             DrawPosition(FALSE, boards[currentMove]);
10741             DisplayBothClocks();
10742             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10743               DisplayComment(currentMove - 1, commentList[currentMove]);
10744         }
10745         (void) StopLoadGameTimer();
10746         gameFileFP = NULL;
10747         cmailOldMove = forwardMostMove;
10748         return FALSE;
10749     } else {
10750         /* currentMoveString is set as a side-effect of yylex */
10751
10752         thinkOutput[0] = NULLCHAR;
10753         MakeMove(fromX, fromY, toX, toY, promoChar);
10754         currentMove = forwardMostMove;
10755         return TRUE;
10756     }
10757 }
10758
10759 /* Load the nth game from the given file */
10760 int
10761 LoadGameFromFile(filename, n, title, useList)
10762      char *filename;
10763      int n;
10764      char *title;
10765      /*Boolean*/ int useList;
10766 {
10767     FILE *f;
10768     char buf[MSG_SIZ];
10769
10770     if (strcmp(filename, "-") == 0) {
10771         f = stdin;
10772         title = "stdin";
10773     } else {
10774         f = fopen(filename, "rb");
10775         if (f == NULL) {
10776           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10777             DisplayError(buf, errno);
10778             return FALSE;
10779         }
10780     }
10781     if (fseek(f, 0, 0) == -1) {
10782         /* f is not seekable; probably a pipe */
10783         useList = FALSE;
10784     }
10785     if (useList && n == 0) {
10786         int error = GameListBuild(f);
10787         if (error) {
10788             DisplayError(_("Cannot build game list"), error);
10789         } else if (!ListEmpty(&gameList) &&
10790                    ((ListGame *) gameList.tailPred)->number > 1) {
10791             GameListPopUp(f, title);
10792             return TRUE;
10793         }
10794         GameListDestroy();
10795         n = 1;
10796     }
10797     if (n == 0) n = 1;
10798     return LoadGame(f, n, title, FALSE);
10799 }
10800
10801
10802 void
10803 MakeRegisteredMove()
10804 {
10805     int fromX, fromY, toX, toY;
10806     char promoChar;
10807     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10808         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10809           case CMAIL_MOVE:
10810           case CMAIL_DRAW:
10811             if (appData.debugMode)
10812               fprintf(debugFP, "Restoring %s for game %d\n",
10813                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10814
10815             thinkOutput[0] = NULLCHAR;
10816             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10817             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10818             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10819             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10820             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10821             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10822             MakeMove(fromX, fromY, toX, toY, promoChar);
10823             ShowMove(fromX, fromY, toX, toY);
10824
10825             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10826               case MT_NONE:
10827               case MT_CHECK:
10828                 break;
10829
10830               case MT_CHECKMATE:
10831               case MT_STAINMATE:
10832                 if (WhiteOnMove(currentMove)) {
10833                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10834                 } else {
10835                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10836                 }
10837                 break;
10838
10839               case MT_STALEMATE:
10840                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10841                 break;
10842             }
10843
10844             break;
10845
10846           case CMAIL_RESIGN:
10847             if (WhiteOnMove(currentMove)) {
10848                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10849             } else {
10850                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10851             }
10852             break;
10853
10854           case CMAIL_ACCEPT:
10855             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10856             break;
10857
10858           default:
10859             break;
10860         }
10861     }
10862
10863     return;
10864 }
10865
10866 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10867 int
10868 CmailLoadGame(f, gameNumber, title, useList)
10869      FILE *f;
10870      int gameNumber;
10871      char *title;
10872      int useList;
10873 {
10874     int retVal;
10875
10876     if (gameNumber > nCmailGames) {
10877         DisplayError(_("No more games in this message"), 0);
10878         return FALSE;
10879     }
10880     if (f == lastLoadGameFP) {
10881         int offset = gameNumber - lastLoadGameNumber;
10882         if (offset == 0) {
10883             cmailMsg[0] = NULLCHAR;
10884             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10885                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10886                 nCmailMovesRegistered--;
10887             }
10888             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10889             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10890                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10891             }
10892         } else {
10893             if (! RegisterMove()) return FALSE;
10894         }
10895     }
10896
10897     retVal = LoadGame(f, gameNumber, title, useList);
10898
10899     /* Make move registered during previous look at this game, if any */
10900     MakeRegisteredMove();
10901
10902     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10903         commentList[currentMove]
10904           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10905         DisplayComment(currentMove - 1, commentList[currentMove]);
10906     }
10907
10908     return retVal;
10909 }
10910
10911 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10912 int
10913 ReloadGame(offset)
10914      int offset;
10915 {
10916     int gameNumber = lastLoadGameNumber + offset;
10917     if (lastLoadGameFP == NULL) {
10918         DisplayError(_("No game has been loaded yet"), 0);
10919         return FALSE;
10920     }
10921     if (gameNumber <= 0) {
10922         DisplayError(_("Can't back up any further"), 0);
10923         return FALSE;
10924     }
10925     if (cmailMsgLoaded) {
10926         return CmailLoadGame(lastLoadGameFP, gameNumber,
10927                              lastLoadGameTitle, lastLoadGameUseList);
10928     } else {
10929         return LoadGame(lastLoadGameFP, gameNumber,
10930                         lastLoadGameTitle, lastLoadGameUseList);
10931     }
10932 }
10933
10934
10935
10936 /* Load the nth game from open file f */
10937 int
10938 LoadGame(f, gameNumber, title, useList)
10939      FILE *f;
10940      int gameNumber;
10941      char *title;
10942      int useList;
10943 {
10944     ChessMove cm;
10945     char buf[MSG_SIZ];
10946     int gn = gameNumber;
10947     ListGame *lg = NULL;
10948     int numPGNTags = 0;
10949     int err;
10950     GameMode oldGameMode;
10951     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10952
10953     if (appData.debugMode)
10954         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10955
10956     if (gameMode == Training )
10957         SetTrainingModeOff();
10958
10959     oldGameMode = gameMode;
10960     if (gameMode != BeginningOfGame) {
10961       Reset(FALSE, TRUE);
10962     }
10963
10964     gameFileFP = f;
10965     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10966         fclose(lastLoadGameFP);
10967     }
10968
10969     if (useList) {
10970         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10971
10972         if (lg) {
10973             fseek(f, lg->offset, 0);
10974             GameListHighlight(gameNumber);
10975             gn = 1;
10976         }
10977         else {
10978             DisplayError(_("Game number out of range"), 0);
10979             return FALSE;
10980         }
10981     } else {
10982         GameListDestroy();
10983         if (fseek(f, 0, 0) == -1) {
10984             if (f == lastLoadGameFP ?
10985                 gameNumber == lastLoadGameNumber + 1 :
10986                 gameNumber == 1) {
10987                 gn = 1;
10988             } else {
10989                 DisplayError(_("Can't seek on game file"), 0);
10990                 return FALSE;
10991             }
10992         }
10993     }
10994     lastLoadGameFP = f;
10995     lastLoadGameNumber = gameNumber;
10996     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10997     lastLoadGameUseList = useList;
10998
10999     yynewfile(f);
11000
11001     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11002       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11003                 lg->gameInfo.black);
11004             DisplayTitle(buf);
11005     } else if (*title != NULLCHAR) {
11006         if (gameNumber > 1) {
11007           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11008             DisplayTitle(buf);
11009         } else {
11010             DisplayTitle(title);
11011         }
11012     }
11013
11014     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11015         gameMode = PlayFromGameFile;
11016         ModeHighlight();
11017     }
11018
11019     currentMove = forwardMostMove = backwardMostMove = 0;
11020     CopyBoard(boards[0], initialPosition);
11021     StopClocks();
11022
11023     /*
11024      * Skip the first gn-1 games in the file.
11025      * Also skip over anything that precedes an identifiable
11026      * start of game marker, to avoid being confused by
11027      * garbage at the start of the file.  Currently
11028      * recognized start of game markers are the move number "1",
11029      * the pattern "gnuchess .* game", the pattern
11030      * "^[#;%] [^ ]* game file", and a PGN tag block.
11031      * A game that starts with one of the latter two patterns
11032      * will also have a move number 1, possibly
11033      * following a position diagram.
11034      * 5-4-02: Let's try being more lenient and allowing a game to
11035      * start with an unnumbered move.  Does that break anything?
11036      */
11037     cm = lastLoadGameStart = EndOfFile;
11038     while (gn > 0) {
11039         yyboardindex = forwardMostMove;
11040         cm = (ChessMove) Myylex();
11041         switch (cm) {
11042           case EndOfFile:
11043             if (cmailMsgLoaded) {
11044                 nCmailGames = CMAIL_MAX_GAMES - gn;
11045             } else {
11046                 Reset(TRUE, TRUE);
11047                 DisplayError(_("Game not found in file"), 0);
11048             }
11049             return FALSE;
11050
11051           case GNUChessGame:
11052           case XBoardGame:
11053             gn--;
11054             lastLoadGameStart = cm;
11055             break;
11056
11057           case MoveNumberOne:
11058             switch (lastLoadGameStart) {
11059               case GNUChessGame:
11060               case XBoardGame:
11061               case PGNTag:
11062                 break;
11063               case MoveNumberOne:
11064               case EndOfFile:
11065                 gn--;           /* count this game */
11066                 lastLoadGameStart = cm;
11067                 break;
11068               default:
11069                 /* impossible */
11070                 break;
11071             }
11072             break;
11073
11074           case PGNTag:
11075             switch (lastLoadGameStart) {
11076               case GNUChessGame:
11077               case PGNTag:
11078               case MoveNumberOne:
11079               case EndOfFile:
11080                 gn--;           /* count this game */
11081                 lastLoadGameStart = cm;
11082                 break;
11083               case XBoardGame:
11084                 lastLoadGameStart = cm; /* game counted already */
11085                 break;
11086               default:
11087                 /* impossible */
11088                 break;
11089             }
11090             if (gn > 0) {
11091                 do {
11092                     yyboardindex = forwardMostMove;
11093                     cm = (ChessMove) Myylex();
11094                 } while (cm == PGNTag || cm == Comment);
11095             }
11096             break;
11097
11098           case WhiteWins:
11099           case BlackWins:
11100           case GameIsDrawn:
11101             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11102                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11103                     != CMAIL_OLD_RESULT) {
11104                     nCmailResults ++ ;
11105                     cmailResult[  CMAIL_MAX_GAMES
11106                                 - gn - 1] = CMAIL_OLD_RESULT;
11107                 }
11108             }
11109             break;
11110
11111           case NormalMove:
11112             /* Only a NormalMove can be at the start of a game
11113              * without a position diagram. */
11114             if (lastLoadGameStart == EndOfFile ) {
11115               gn--;
11116               lastLoadGameStart = MoveNumberOne;
11117             }
11118             break;
11119
11120           default:
11121             break;
11122         }
11123     }
11124
11125     if (appData.debugMode)
11126       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11127
11128     if (cm == XBoardGame) {
11129         /* Skip any header junk before position diagram and/or move 1 */
11130         for (;;) {
11131             yyboardindex = forwardMostMove;
11132             cm = (ChessMove) Myylex();
11133
11134             if (cm == EndOfFile ||
11135                 cm == GNUChessGame || cm == XBoardGame) {
11136                 /* Empty game; pretend end-of-file and handle later */
11137                 cm = EndOfFile;
11138                 break;
11139             }
11140
11141             if (cm == MoveNumberOne || cm == PositionDiagram ||
11142                 cm == PGNTag || cm == Comment)
11143               break;
11144         }
11145     } else if (cm == GNUChessGame) {
11146         if (gameInfo.event != NULL) {
11147             free(gameInfo.event);
11148         }
11149         gameInfo.event = StrSave(yy_text);
11150     }
11151
11152     startedFromSetupPosition = FALSE;
11153     while (cm == PGNTag) {
11154         if (appData.debugMode)
11155           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11156         err = ParsePGNTag(yy_text, &gameInfo);
11157         if (!err) numPGNTags++;
11158
11159         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11160         if(gameInfo.variant != oldVariant) {
11161             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11162             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11163             InitPosition(TRUE);
11164             oldVariant = gameInfo.variant;
11165             if (appData.debugMode)
11166               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11167         }
11168
11169
11170         if (gameInfo.fen != NULL) {
11171           Board initial_position;
11172           startedFromSetupPosition = TRUE;
11173           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11174             Reset(TRUE, TRUE);
11175             DisplayError(_("Bad FEN position in file"), 0);
11176             return FALSE;
11177           }
11178           CopyBoard(boards[0], initial_position);
11179           if (blackPlaysFirst) {
11180             currentMove = forwardMostMove = backwardMostMove = 1;
11181             CopyBoard(boards[1], initial_position);
11182             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11183             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11184             timeRemaining[0][1] = whiteTimeRemaining;
11185             timeRemaining[1][1] = blackTimeRemaining;
11186             if (commentList[0] != NULL) {
11187               commentList[1] = commentList[0];
11188               commentList[0] = NULL;
11189             }
11190           } else {
11191             currentMove = forwardMostMove = backwardMostMove = 0;
11192           }
11193           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11194           {   int i;
11195               initialRulePlies = FENrulePlies;
11196               for( i=0; i< nrCastlingRights; i++ )
11197                   initialRights[i] = initial_position[CASTLING][i];
11198           }
11199           yyboardindex = forwardMostMove;
11200           free(gameInfo.fen);
11201           gameInfo.fen = NULL;
11202         }
11203
11204         yyboardindex = forwardMostMove;
11205         cm = (ChessMove) Myylex();
11206
11207         /* Handle comments interspersed among the tags */
11208         while (cm == Comment) {
11209             char *p;
11210             if (appData.debugMode)
11211               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11212             p = yy_text;
11213             AppendComment(currentMove, p, FALSE);
11214             yyboardindex = forwardMostMove;
11215             cm = (ChessMove) Myylex();
11216         }
11217     }
11218
11219     /* don't rely on existence of Event tag since if game was
11220      * pasted from clipboard the Event tag may not exist
11221      */
11222     if (numPGNTags > 0){
11223         char *tags;
11224         if (gameInfo.variant == VariantNormal) {
11225           VariantClass v = StringToVariant(gameInfo.event);
11226           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11227           if(v < VariantShogi) gameInfo.variant = v;
11228         }
11229         if (!matchMode) {
11230           if( appData.autoDisplayTags ) {
11231             tags = PGNTags(&gameInfo);
11232             TagsPopUp(tags, CmailMsg());
11233             free(tags);
11234           }
11235         }
11236     } else {
11237         /* Make something up, but don't display it now */
11238         SetGameInfo();
11239         TagsPopDown();
11240     }
11241
11242     if (cm == PositionDiagram) {
11243         int i, j;
11244         char *p;
11245         Board initial_position;
11246
11247         if (appData.debugMode)
11248           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11249
11250         if (!startedFromSetupPosition) {
11251             p = yy_text;
11252             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11253               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11254                 switch (*p) {
11255                   case '{':
11256                   case '[':
11257                   case '-':
11258                   case ' ':
11259                   case '\t':
11260                   case '\n':
11261                   case '\r':
11262                     break;
11263                   default:
11264                     initial_position[i][j++] = CharToPiece(*p);
11265                     break;
11266                 }
11267             while (*p == ' ' || *p == '\t' ||
11268                    *p == '\n' || *p == '\r') p++;
11269
11270             if (strncmp(p, "black", strlen("black"))==0)
11271               blackPlaysFirst = TRUE;
11272             else
11273               blackPlaysFirst = FALSE;
11274             startedFromSetupPosition = TRUE;
11275
11276             CopyBoard(boards[0], initial_position);
11277             if (blackPlaysFirst) {
11278                 currentMove = forwardMostMove = backwardMostMove = 1;
11279                 CopyBoard(boards[1], initial_position);
11280                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11281                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11282                 timeRemaining[0][1] = whiteTimeRemaining;
11283                 timeRemaining[1][1] = blackTimeRemaining;
11284                 if (commentList[0] != NULL) {
11285                     commentList[1] = commentList[0];
11286                     commentList[0] = NULL;
11287                 }
11288             } else {
11289                 currentMove = forwardMostMove = backwardMostMove = 0;
11290             }
11291         }
11292         yyboardindex = forwardMostMove;
11293         cm = (ChessMove) Myylex();
11294     }
11295
11296     if (first.pr == NoProc) {
11297         StartChessProgram(&first);
11298     }
11299     InitChessProgram(&first, FALSE);
11300     SendToProgram("force\n", &first);
11301     if (startedFromSetupPosition) {
11302         SendBoard(&first, forwardMostMove);
11303     if (appData.debugMode) {
11304         fprintf(debugFP, "Load Game\n");
11305     }
11306         DisplayBothClocks();
11307     }
11308
11309     /* [HGM] server: flag to write setup moves in broadcast file as one */
11310     loadFlag = appData.suppressLoadMoves;
11311
11312     while (cm == Comment) {
11313         char *p;
11314         if (appData.debugMode)
11315           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11316         p = yy_text;
11317         AppendComment(currentMove, p, FALSE);
11318         yyboardindex = forwardMostMove;
11319         cm = (ChessMove) Myylex();
11320     }
11321
11322     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11323         cm == WhiteWins || cm == BlackWins ||
11324         cm == GameIsDrawn || cm == GameUnfinished) {
11325         DisplayMessage("", _("No moves in game"));
11326         if (cmailMsgLoaded) {
11327             if (appData.debugMode)
11328               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11329             ClearHighlights();
11330             flipView = FALSE;
11331         }
11332         DrawPosition(FALSE, boards[currentMove]);
11333         DisplayBothClocks();
11334         gameMode = EditGame;
11335         ModeHighlight();
11336         gameFileFP = NULL;
11337         cmailOldMove = 0;
11338         return TRUE;
11339     }
11340
11341     // [HGM] PV info: routine tests if comment empty
11342     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11343         DisplayComment(currentMove - 1, commentList[currentMove]);
11344     }
11345     if (!matchMode && appData.timeDelay != 0)
11346       DrawPosition(FALSE, boards[currentMove]);
11347
11348     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11349       programStats.ok_to_send = 1;
11350     }
11351
11352     /* if the first token after the PGN tags is a move
11353      * and not move number 1, retrieve it from the parser
11354      */
11355     if (cm != MoveNumberOne)
11356         LoadGameOneMove(cm);
11357
11358     /* load the remaining moves from the file */
11359     while (LoadGameOneMove(EndOfFile)) {
11360       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11361       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11362     }
11363
11364     /* rewind to the start of the game */
11365     currentMove = backwardMostMove;
11366
11367     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11368
11369     if (oldGameMode == AnalyzeFile ||
11370         oldGameMode == AnalyzeMode) {
11371       AnalyzeFileEvent();
11372     }
11373
11374     if (matchMode || appData.timeDelay == 0) {
11375       ToEndEvent();
11376       gameMode = EditGame;
11377       ModeHighlight();
11378     } else if (appData.timeDelay > 0) {
11379       AutoPlayGameLoop();
11380     }
11381
11382     if (appData.debugMode)
11383         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11384
11385     loadFlag = 0; /* [HGM] true game starts */
11386     return TRUE;
11387 }
11388
11389 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11390 int
11391 ReloadPosition(offset)
11392      int offset;
11393 {
11394     int positionNumber = lastLoadPositionNumber + offset;
11395     if (lastLoadPositionFP == NULL) {
11396         DisplayError(_("No position has been loaded yet"), 0);
11397         return FALSE;
11398     }
11399     if (positionNumber <= 0) {
11400         DisplayError(_("Can't back up any further"), 0);
11401         return FALSE;
11402     }
11403     return LoadPosition(lastLoadPositionFP, positionNumber,
11404                         lastLoadPositionTitle);
11405 }
11406
11407 /* Load the nth position from the given file */
11408 int
11409 LoadPositionFromFile(filename, n, title)
11410      char *filename;
11411      int n;
11412      char *title;
11413 {
11414     FILE *f;
11415     char buf[MSG_SIZ];
11416
11417     if (strcmp(filename, "-") == 0) {
11418         return LoadPosition(stdin, n, "stdin");
11419     } else {
11420         f = fopen(filename, "rb");
11421         if (f == NULL) {
11422             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11423             DisplayError(buf, errno);
11424             return FALSE;
11425         } else {
11426             return LoadPosition(f, n, title);
11427         }
11428     }
11429 }
11430
11431 /* Load the nth position from the given open file, and close it */
11432 int
11433 LoadPosition(f, positionNumber, title)
11434      FILE *f;
11435      int positionNumber;
11436      char *title;
11437 {
11438     char *p, line[MSG_SIZ];
11439     Board initial_position;
11440     int i, j, fenMode, pn;
11441
11442     if (gameMode == Training )
11443         SetTrainingModeOff();
11444
11445     if (gameMode != BeginningOfGame) {
11446         Reset(FALSE, TRUE);
11447     }
11448     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11449         fclose(lastLoadPositionFP);
11450     }
11451     if (positionNumber == 0) positionNumber = 1;
11452     lastLoadPositionFP = f;
11453     lastLoadPositionNumber = positionNumber;
11454     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11455     if (first.pr == NoProc) {
11456       StartChessProgram(&first);
11457       InitChessProgram(&first, FALSE);
11458     }
11459     pn = positionNumber;
11460     if (positionNumber < 0) {
11461         /* Negative position number means to seek to that byte offset */
11462         if (fseek(f, -positionNumber, 0) == -1) {
11463             DisplayError(_("Can't seek on position file"), 0);
11464             return FALSE;
11465         };
11466         pn = 1;
11467     } else {
11468         if (fseek(f, 0, 0) == -1) {
11469             if (f == lastLoadPositionFP ?
11470                 positionNumber == lastLoadPositionNumber + 1 :
11471                 positionNumber == 1) {
11472                 pn = 1;
11473             } else {
11474                 DisplayError(_("Can't seek on position file"), 0);
11475                 return FALSE;
11476             }
11477         }
11478     }
11479     /* See if this file is FEN or old-style xboard */
11480     if (fgets(line, MSG_SIZ, f) == NULL) {
11481         DisplayError(_("Position not found in file"), 0);
11482         return FALSE;
11483     }
11484     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11485     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11486
11487     if (pn >= 2) {
11488         if (fenMode || line[0] == '#') pn--;
11489         while (pn > 0) {
11490             /* skip positions before number pn */
11491             if (fgets(line, MSG_SIZ, f) == NULL) {
11492                 Reset(TRUE, TRUE);
11493                 DisplayError(_("Position not found in file"), 0);
11494                 return FALSE;
11495             }
11496             if (fenMode || line[0] == '#') pn--;
11497         }
11498     }
11499
11500     if (fenMode) {
11501         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11502             DisplayError(_("Bad FEN position in file"), 0);
11503             return FALSE;
11504         }
11505     } else {
11506         (void) fgets(line, MSG_SIZ, f);
11507         (void) fgets(line, MSG_SIZ, f);
11508
11509         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11510             (void) fgets(line, MSG_SIZ, f);
11511             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11512                 if (*p == ' ')
11513                   continue;
11514                 initial_position[i][j++] = CharToPiece(*p);
11515             }
11516         }
11517
11518         blackPlaysFirst = FALSE;
11519         if (!feof(f)) {
11520             (void) fgets(line, MSG_SIZ, f);
11521             if (strncmp(line, "black", strlen("black"))==0)
11522               blackPlaysFirst = TRUE;
11523         }
11524     }
11525     startedFromSetupPosition = TRUE;
11526
11527     SendToProgram("force\n", &first);
11528     CopyBoard(boards[0], initial_position);
11529     if (blackPlaysFirst) {
11530         currentMove = forwardMostMove = backwardMostMove = 1;
11531         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11532         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11533         CopyBoard(boards[1], initial_position);
11534         DisplayMessage("", _("Black to play"));
11535     } else {
11536         currentMove = forwardMostMove = backwardMostMove = 0;
11537         DisplayMessage("", _("White to play"));
11538     }
11539     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11540     SendBoard(&first, forwardMostMove);
11541     if (appData.debugMode) {
11542 int i, j;
11543   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11544   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11545         fprintf(debugFP, "Load Position\n");
11546     }
11547
11548     if (positionNumber > 1) {
11549       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11550         DisplayTitle(line);
11551     } else {
11552         DisplayTitle(title);
11553     }
11554     gameMode = EditGame;
11555     ModeHighlight();
11556     ResetClocks();
11557     timeRemaining[0][1] = whiteTimeRemaining;
11558     timeRemaining[1][1] = blackTimeRemaining;
11559     DrawPosition(FALSE, boards[currentMove]);
11560
11561     return TRUE;
11562 }
11563
11564
11565 void
11566 CopyPlayerNameIntoFileName(dest, src)
11567      char **dest, *src;
11568 {
11569     while (*src != NULLCHAR && *src != ',') {
11570         if (*src == ' ') {
11571             *(*dest)++ = '_';
11572             src++;
11573         } else {
11574             *(*dest)++ = *src++;
11575         }
11576     }
11577 }
11578
11579 char *DefaultFileName(ext)
11580      char *ext;
11581 {
11582     static char def[MSG_SIZ];
11583     char *p;
11584
11585     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11586         p = def;
11587         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11588         *p++ = '-';
11589         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11590         *p++ = '.';
11591         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11592     } else {
11593         def[0] = NULLCHAR;
11594     }
11595     return def;
11596 }
11597
11598 /* Save the current game to the given file */
11599 int
11600 SaveGameToFile(filename, append)
11601      char *filename;
11602      int append;
11603 {
11604     FILE *f;
11605     char buf[MSG_SIZ];
11606     int result;
11607
11608     if (strcmp(filename, "-") == 0) {
11609         return SaveGame(stdout, 0, NULL);
11610     } else {
11611         f = fopen(filename, append ? "a" : "w");
11612         if (f == NULL) {
11613             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11614             DisplayError(buf, errno);
11615             return FALSE;
11616         } else {
11617             safeStrCpy(buf, lastMsg, MSG_SIZ);
11618             DisplayMessage(_("Waiting for access to save file"), "");
11619             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11620             DisplayMessage(_("Saving game"), "");
11621             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11622             result = SaveGame(f, 0, NULL);
11623             DisplayMessage(buf, "");
11624             return result;
11625         }
11626     }
11627 }
11628
11629 char *
11630 SavePart(str)
11631      char *str;
11632 {
11633     static char buf[MSG_SIZ];
11634     char *p;
11635
11636     p = strchr(str, ' ');
11637     if (p == NULL) return str;
11638     strncpy(buf, str, p - str);
11639     buf[p - str] = NULLCHAR;
11640     return buf;
11641 }
11642
11643 #define PGN_MAX_LINE 75
11644
11645 #define PGN_SIDE_WHITE  0
11646 #define PGN_SIDE_BLACK  1
11647
11648 /* [AS] */
11649 static int FindFirstMoveOutOfBook( int side )
11650 {
11651     int result = -1;
11652
11653     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11654         int index = backwardMostMove;
11655         int has_book_hit = 0;
11656
11657         if( (index % 2) != side ) {
11658             index++;
11659         }
11660
11661         while( index < forwardMostMove ) {
11662             /* Check to see if engine is in book */
11663             int depth = pvInfoList[index].depth;
11664             int score = pvInfoList[index].score;
11665             int in_book = 0;
11666
11667             if( depth <= 2 ) {
11668                 in_book = 1;
11669             }
11670             else if( score == 0 && depth == 63 ) {
11671                 in_book = 1; /* Zappa */
11672             }
11673             else if( score == 2 && depth == 99 ) {
11674                 in_book = 1; /* Abrok */
11675             }
11676
11677             has_book_hit += in_book;
11678
11679             if( ! in_book ) {
11680                 result = index;
11681
11682                 break;
11683             }
11684
11685             index += 2;
11686         }
11687     }
11688
11689     return result;
11690 }
11691
11692 /* [AS] */
11693 void GetOutOfBookInfo( char * buf )
11694 {
11695     int oob[2];
11696     int i;
11697     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11698
11699     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11700     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11701
11702     *buf = '\0';
11703
11704     if( oob[0] >= 0 || oob[1] >= 0 ) {
11705         for( i=0; i<2; i++ ) {
11706             int idx = oob[i];
11707
11708             if( idx >= 0 ) {
11709                 if( i > 0 && oob[0] >= 0 ) {
11710                     strcat( buf, "   " );
11711                 }
11712
11713                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11714                 sprintf( buf+strlen(buf), "%s%.2f",
11715                     pvInfoList[idx].score >= 0 ? "+" : "",
11716                     pvInfoList[idx].score / 100.0 );
11717             }
11718         }
11719     }
11720 }
11721
11722 /* Save game in PGN style and close the file */
11723 int
11724 SaveGamePGN(f)
11725      FILE *f;
11726 {
11727     int i, offset, linelen, newblock;
11728     time_t tm;
11729 //    char *movetext;
11730     char numtext[32];
11731     int movelen, numlen, blank;
11732     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11733
11734     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11735
11736     tm = time((time_t *) NULL);
11737
11738     PrintPGNTags(f, &gameInfo);
11739
11740     if (backwardMostMove > 0 || startedFromSetupPosition) {
11741         char *fen = PositionToFEN(backwardMostMove, NULL);
11742         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11743         fprintf(f, "\n{--------------\n");
11744         PrintPosition(f, backwardMostMove);
11745         fprintf(f, "--------------}\n");
11746         free(fen);
11747     }
11748     else {
11749         /* [AS] Out of book annotation */
11750         if( appData.saveOutOfBookInfo ) {
11751             char buf[64];
11752
11753             GetOutOfBookInfo( buf );
11754
11755             if( buf[0] != '\0' ) {
11756                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11757             }
11758         }
11759
11760         fprintf(f, "\n");
11761     }
11762
11763     i = backwardMostMove;
11764     linelen = 0;
11765     newblock = TRUE;
11766
11767     while (i < forwardMostMove) {
11768         /* Print comments preceding this move */
11769         if (commentList[i] != NULL) {
11770             if (linelen > 0) fprintf(f, "\n");
11771             fprintf(f, "%s", commentList[i]);
11772             linelen = 0;
11773             newblock = TRUE;
11774         }
11775
11776         /* Format move number */
11777         if ((i % 2) == 0)
11778           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11779         else
11780           if (newblock)
11781             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11782           else
11783             numtext[0] = NULLCHAR;
11784
11785         numlen = strlen(numtext);
11786         newblock = FALSE;
11787
11788         /* Print move number */
11789         blank = linelen > 0 && numlen > 0;
11790         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11791             fprintf(f, "\n");
11792             linelen = 0;
11793             blank = 0;
11794         }
11795         if (blank) {
11796             fprintf(f, " ");
11797             linelen++;
11798         }
11799         fprintf(f, "%s", numtext);
11800         linelen += numlen;
11801
11802         /* Get move */
11803         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11804         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11805
11806         /* Print move */
11807         blank = linelen > 0 && movelen > 0;
11808         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11809             fprintf(f, "\n");
11810             linelen = 0;
11811             blank = 0;
11812         }
11813         if (blank) {
11814             fprintf(f, " ");
11815             linelen++;
11816         }
11817         fprintf(f, "%s", move_buffer);
11818         linelen += movelen;
11819
11820         /* [AS] Add PV info if present */
11821         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11822             /* [HGM] add time */
11823             char buf[MSG_SIZ]; int seconds;
11824
11825             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11826
11827             if( seconds <= 0)
11828               buf[0] = 0;
11829             else
11830               if( seconds < 30 )
11831                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11832               else
11833                 {
11834                   seconds = (seconds + 4)/10; // round to full seconds
11835                   if( seconds < 60 )
11836                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11837                   else
11838                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11839                 }
11840
11841             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11842                       pvInfoList[i].score >= 0 ? "+" : "",
11843                       pvInfoList[i].score / 100.0,
11844                       pvInfoList[i].depth,
11845                       buf );
11846
11847             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11848
11849             /* Print score/depth */
11850             blank = linelen > 0 && movelen > 0;
11851             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11852                 fprintf(f, "\n");
11853                 linelen = 0;
11854                 blank = 0;
11855             }
11856             if (blank) {
11857                 fprintf(f, " ");
11858                 linelen++;
11859             }
11860             fprintf(f, "%s", move_buffer);
11861             linelen += movelen;
11862         }
11863
11864         i++;
11865     }
11866
11867     /* Start a new line */
11868     if (linelen > 0) fprintf(f, "\n");
11869
11870     /* Print comments after last move */
11871     if (commentList[i] != NULL) {
11872         fprintf(f, "%s\n", commentList[i]);
11873     }
11874
11875     /* Print result */
11876     if (gameInfo.resultDetails != NULL &&
11877         gameInfo.resultDetails[0] != NULLCHAR) {
11878         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11879                 PGNResult(gameInfo.result));
11880     } else {
11881         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11882     }
11883
11884     fclose(f);
11885     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11886     return TRUE;
11887 }
11888
11889 /* Save game in old style and close the file */
11890 int
11891 SaveGameOldStyle(f)
11892      FILE *f;
11893 {
11894     int i, offset;
11895     time_t tm;
11896
11897     tm = time((time_t *) NULL);
11898
11899     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11900     PrintOpponents(f);
11901
11902     if (backwardMostMove > 0 || startedFromSetupPosition) {
11903         fprintf(f, "\n[--------------\n");
11904         PrintPosition(f, backwardMostMove);
11905         fprintf(f, "--------------]\n");
11906     } else {
11907         fprintf(f, "\n");
11908     }
11909
11910     i = backwardMostMove;
11911     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11912
11913     while (i < forwardMostMove) {
11914         if (commentList[i] != NULL) {
11915             fprintf(f, "[%s]\n", commentList[i]);
11916         }
11917
11918         if ((i % 2) == 1) {
11919             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11920             i++;
11921         } else {
11922             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11923             i++;
11924             if (commentList[i] != NULL) {
11925                 fprintf(f, "\n");
11926                 continue;
11927             }
11928             if (i >= forwardMostMove) {
11929                 fprintf(f, "\n");
11930                 break;
11931             }
11932             fprintf(f, "%s\n", parseList[i]);
11933             i++;
11934         }
11935     }
11936
11937     if (commentList[i] != NULL) {
11938         fprintf(f, "[%s]\n", commentList[i]);
11939     }
11940
11941     /* This isn't really the old style, but it's close enough */
11942     if (gameInfo.resultDetails != NULL &&
11943         gameInfo.resultDetails[0] != NULLCHAR) {
11944         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11945                 gameInfo.resultDetails);
11946     } else {
11947         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11948     }
11949
11950     fclose(f);
11951     return TRUE;
11952 }
11953
11954 /* Save the current game to open file f and close the file */
11955 int
11956 SaveGame(f, dummy, dummy2)
11957      FILE *f;
11958      int dummy;
11959      char *dummy2;
11960 {
11961     if (gameMode == EditPosition) EditPositionDone(TRUE);
11962     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11963     if (appData.oldSaveStyle)
11964       return SaveGameOldStyle(f);
11965     else
11966       return SaveGamePGN(f);
11967 }
11968
11969 /* Save the current position to the given file */
11970 int
11971 SavePositionToFile(filename)
11972      char *filename;
11973 {
11974     FILE *f;
11975     char buf[MSG_SIZ];
11976
11977     if (strcmp(filename, "-") == 0) {
11978         return SavePosition(stdout, 0, NULL);
11979     } else {
11980         f = fopen(filename, "a");
11981         if (f == NULL) {
11982             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11983             DisplayError(buf, errno);
11984             return FALSE;
11985         } else {
11986             safeStrCpy(buf, lastMsg, MSG_SIZ);
11987             DisplayMessage(_("Waiting for access to save file"), "");
11988             flock(fileno(f), LOCK_EX); // [HGM] lock
11989             DisplayMessage(_("Saving position"), "");
11990             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11991             SavePosition(f, 0, NULL);
11992             DisplayMessage(buf, "");
11993             return TRUE;
11994         }
11995     }
11996 }
11997
11998 /* Save the current position to the given open file and close the file */
11999 int
12000 SavePosition(f, dummy, dummy2)
12001      FILE *f;
12002      int dummy;
12003      char *dummy2;
12004 {
12005     time_t tm;
12006     char *fen;
12007
12008     if (gameMode == EditPosition) EditPositionDone(TRUE);
12009     if (appData.oldSaveStyle) {
12010         tm = time((time_t *) NULL);
12011
12012         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12013         PrintOpponents(f);
12014         fprintf(f, "[--------------\n");
12015         PrintPosition(f, currentMove);
12016         fprintf(f, "--------------]\n");
12017     } else {
12018         fen = PositionToFEN(currentMove, NULL);
12019         fprintf(f, "%s\n", fen);
12020         free(fen);
12021     }
12022     fclose(f);
12023     return TRUE;
12024 }
12025
12026 void
12027 ReloadCmailMsgEvent(unregister)
12028      int unregister;
12029 {
12030 #if !WIN32
12031     static char *inFilename = NULL;
12032     static char *outFilename;
12033     int i;
12034     struct stat inbuf, outbuf;
12035     int status;
12036
12037     /* Any registered moves are unregistered if unregister is set, */
12038     /* i.e. invoked by the signal handler */
12039     if (unregister) {
12040         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12041             cmailMoveRegistered[i] = FALSE;
12042             if (cmailCommentList[i] != NULL) {
12043                 free(cmailCommentList[i]);
12044                 cmailCommentList[i] = NULL;
12045             }
12046         }
12047         nCmailMovesRegistered = 0;
12048     }
12049
12050     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12051         cmailResult[i] = CMAIL_NOT_RESULT;
12052     }
12053     nCmailResults = 0;
12054
12055     if (inFilename == NULL) {
12056         /* Because the filenames are static they only get malloced once  */
12057         /* and they never get freed                                      */
12058         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12059         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12060
12061         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12062         sprintf(outFilename, "%s.out", appData.cmailGameName);
12063     }
12064
12065     status = stat(outFilename, &outbuf);
12066     if (status < 0) {
12067         cmailMailedMove = FALSE;
12068     } else {
12069         status = stat(inFilename, &inbuf);
12070         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12071     }
12072
12073     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12074        counts the games, notes how each one terminated, etc.
12075
12076        It would be nice to remove this kludge and instead gather all
12077        the information while building the game list.  (And to keep it
12078        in the game list nodes instead of having a bunch of fixed-size
12079        parallel arrays.)  Note this will require getting each game's
12080        termination from the PGN tags, as the game list builder does
12081        not process the game moves.  --mann
12082        */
12083     cmailMsgLoaded = TRUE;
12084     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12085
12086     /* Load first game in the file or popup game menu */
12087     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12088
12089 #endif /* !WIN32 */
12090     return;
12091 }
12092
12093 int
12094 RegisterMove()
12095 {
12096     FILE *f;
12097     char string[MSG_SIZ];
12098
12099     if (   cmailMailedMove
12100         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12101         return TRUE;            /* Allow free viewing  */
12102     }
12103
12104     /* Unregister move to ensure that we don't leave RegisterMove        */
12105     /* with the move registered when the conditions for registering no   */
12106     /* longer hold                                                       */
12107     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12108         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12109         nCmailMovesRegistered --;
12110
12111         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12112           {
12113               free(cmailCommentList[lastLoadGameNumber - 1]);
12114               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12115           }
12116     }
12117
12118     if (cmailOldMove == -1) {
12119         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12120         return FALSE;
12121     }
12122
12123     if (currentMove > cmailOldMove + 1) {
12124         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12125         return FALSE;
12126     }
12127
12128     if (currentMove < cmailOldMove) {
12129         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12130         return FALSE;
12131     }
12132
12133     if (forwardMostMove > currentMove) {
12134         /* Silently truncate extra moves */
12135         TruncateGame();
12136     }
12137
12138     if (   (currentMove == cmailOldMove + 1)
12139         || (   (currentMove == cmailOldMove)
12140             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12141                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12142         if (gameInfo.result != GameUnfinished) {
12143             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12144         }
12145
12146         if (commentList[currentMove] != NULL) {
12147             cmailCommentList[lastLoadGameNumber - 1]
12148               = StrSave(commentList[currentMove]);
12149         }
12150         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12151
12152         if (appData.debugMode)
12153           fprintf(debugFP, "Saving %s for game %d\n",
12154                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12155
12156         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12157
12158         f = fopen(string, "w");
12159         if (appData.oldSaveStyle) {
12160             SaveGameOldStyle(f); /* also closes the file */
12161
12162             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12163             f = fopen(string, "w");
12164             SavePosition(f, 0, NULL); /* also closes the file */
12165         } else {
12166             fprintf(f, "{--------------\n");
12167             PrintPosition(f, currentMove);
12168             fprintf(f, "--------------}\n\n");
12169
12170             SaveGame(f, 0, NULL); /* also closes the file*/
12171         }
12172
12173         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12174         nCmailMovesRegistered ++;
12175     } else if (nCmailGames == 1) {
12176         DisplayError(_("You have not made a move yet"), 0);
12177         return FALSE;
12178     }
12179
12180     return TRUE;
12181 }
12182
12183 void
12184 MailMoveEvent()
12185 {
12186 #if !WIN32
12187     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12188     FILE *commandOutput;
12189     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12190     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12191     int nBuffers;
12192     int i;
12193     int archived;
12194     char *arcDir;
12195
12196     if (! cmailMsgLoaded) {
12197         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12198         return;
12199     }
12200
12201     if (nCmailGames == nCmailResults) {
12202         DisplayError(_("No unfinished games"), 0);
12203         return;
12204     }
12205
12206 #if CMAIL_PROHIBIT_REMAIL
12207     if (cmailMailedMove) {
12208       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);
12209         DisplayError(msg, 0);
12210         return;
12211     }
12212 #endif
12213
12214     if (! (cmailMailedMove || RegisterMove())) return;
12215
12216     if (   cmailMailedMove
12217         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12218       snprintf(string, MSG_SIZ, partCommandString,
12219                appData.debugMode ? " -v" : "", appData.cmailGameName);
12220         commandOutput = popen(string, "r");
12221
12222         if (commandOutput == NULL) {
12223             DisplayError(_("Failed to invoke cmail"), 0);
12224         } else {
12225             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12226                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12227             }
12228             if (nBuffers > 1) {
12229                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12230                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12231                 nBytes = MSG_SIZ - 1;
12232             } else {
12233                 (void) memcpy(msg, buffer, nBytes);
12234             }
12235             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12236
12237             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12238                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12239
12240                 archived = TRUE;
12241                 for (i = 0; i < nCmailGames; i ++) {
12242                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12243                         archived = FALSE;
12244                     }
12245                 }
12246                 if (   archived
12247                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12248                         != NULL)) {
12249                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12250                            arcDir,
12251                            appData.cmailGameName,
12252                            gameInfo.date);
12253                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12254                     cmailMsgLoaded = FALSE;
12255                 }
12256             }
12257
12258             DisplayInformation(msg);
12259             pclose(commandOutput);
12260         }
12261     } else {
12262         if ((*cmailMsg) != '\0') {
12263             DisplayInformation(cmailMsg);
12264         }
12265     }
12266
12267     return;
12268 #endif /* !WIN32 */
12269 }
12270
12271 char *
12272 CmailMsg()
12273 {
12274 #if WIN32
12275     return NULL;
12276 #else
12277     int  prependComma = 0;
12278     char number[5];
12279     char string[MSG_SIZ];       /* Space for game-list */
12280     int  i;
12281
12282     if (!cmailMsgLoaded) return "";
12283
12284     if (cmailMailedMove) {
12285       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12286     } else {
12287         /* Create a list of games left */
12288       snprintf(string, MSG_SIZ, "[");
12289         for (i = 0; i < nCmailGames; i ++) {
12290             if (! (   cmailMoveRegistered[i]
12291                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12292                 if (prependComma) {
12293                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12294                 } else {
12295                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12296                     prependComma = 1;
12297                 }
12298
12299                 strcat(string, number);
12300             }
12301         }
12302         strcat(string, "]");
12303
12304         if (nCmailMovesRegistered + nCmailResults == 0) {
12305             switch (nCmailGames) {
12306               case 1:
12307                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12308                 break;
12309
12310               case 2:
12311                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12312                 break;
12313
12314               default:
12315                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12316                          nCmailGames);
12317                 break;
12318             }
12319         } else {
12320             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12321               case 1:
12322                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12323                          string);
12324                 break;
12325
12326               case 0:
12327                 if (nCmailResults == nCmailGames) {
12328                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12329                 } else {
12330                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12331                 }
12332                 break;
12333
12334               default:
12335                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12336                          string);
12337             }
12338         }
12339     }
12340     return cmailMsg;
12341 #endif /* WIN32 */
12342 }
12343
12344 void
12345 ResetGameEvent()
12346 {
12347     if (gameMode == Training)
12348       SetTrainingModeOff();
12349
12350     Reset(TRUE, TRUE);
12351     cmailMsgLoaded = FALSE;
12352     if (appData.icsActive) {
12353       SendToICS(ics_prefix);
12354       SendToICS("refresh\n");
12355     }
12356 }
12357
12358 void
12359 ExitEvent(status)
12360      int status;
12361 {
12362     exiting++;
12363     if (exiting > 2) {
12364       /* Give up on clean exit */
12365       exit(status);
12366     }
12367     if (exiting > 1) {
12368       /* Keep trying for clean exit */
12369       return;
12370     }
12371
12372     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12373
12374     if (telnetISR != NULL) {
12375       RemoveInputSource(telnetISR);
12376     }
12377     if (icsPR != NoProc) {
12378       DestroyChildProcess(icsPR, TRUE);
12379     }
12380
12381     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12382     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12383
12384     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12385     /* make sure this other one finishes before killing it!                  */
12386     if(endingGame) { int count = 0;
12387         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12388         while(endingGame && count++ < 10) DoSleep(1);
12389         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12390     }
12391
12392     /* Kill off chess programs */
12393     if (first.pr != NoProc) {
12394         ExitAnalyzeMode();
12395
12396         DoSleep( appData.delayBeforeQuit );
12397         SendToProgram("quit\n", &first);
12398         DoSleep( appData.delayAfterQuit );
12399         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12400     }
12401     if (second.pr != NoProc) {
12402         DoSleep( appData.delayBeforeQuit );
12403         SendToProgram("quit\n", &second);
12404         DoSleep( appData.delayAfterQuit );
12405         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12406     }
12407     if (first.isr != NULL) {
12408         RemoveInputSource(first.isr);
12409     }
12410     if (second.isr != NULL) {
12411         RemoveInputSource(second.isr);
12412     }
12413
12414     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12415     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12416
12417     ShutDownFrontEnd();
12418     exit(status);
12419 }
12420
12421 void
12422 PauseEvent()
12423 {
12424     if (appData.debugMode)
12425         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12426     if (pausing) {
12427         pausing = FALSE;
12428         ModeHighlight();
12429         if (gameMode == MachinePlaysWhite ||
12430             gameMode == MachinePlaysBlack) {
12431             StartClocks();
12432         } else {
12433             DisplayBothClocks();
12434         }
12435         if (gameMode == PlayFromGameFile) {
12436             if (appData.timeDelay >= 0)
12437                 AutoPlayGameLoop();
12438         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12439             Reset(FALSE, TRUE);
12440             SendToICS(ics_prefix);
12441             SendToICS("refresh\n");
12442         } else if (currentMove < forwardMostMove) {
12443             ForwardInner(forwardMostMove);
12444         }
12445         pauseExamInvalid = FALSE;
12446     } else {
12447         switch (gameMode) {
12448           default:
12449             return;
12450           case IcsExamining:
12451             pauseExamForwardMostMove = forwardMostMove;
12452             pauseExamInvalid = FALSE;
12453             /* fall through */
12454           case IcsObserving:
12455           case IcsPlayingWhite:
12456           case IcsPlayingBlack:
12457             pausing = TRUE;
12458             ModeHighlight();
12459             return;
12460           case PlayFromGameFile:
12461             (void) StopLoadGameTimer();
12462             pausing = TRUE;
12463             ModeHighlight();
12464             break;
12465           case BeginningOfGame:
12466             if (appData.icsActive) return;
12467             /* else fall through */
12468           case MachinePlaysWhite:
12469           case MachinePlaysBlack:
12470           case TwoMachinesPlay:
12471             if (forwardMostMove == 0)
12472               return;           /* don't pause if no one has moved */
12473             if ((gameMode == MachinePlaysWhite &&
12474                  !WhiteOnMove(forwardMostMove)) ||
12475                 (gameMode == MachinePlaysBlack &&
12476                  WhiteOnMove(forwardMostMove))) {
12477                 StopClocks();
12478             }
12479             pausing = TRUE;
12480             ModeHighlight();
12481             break;
12482         }
12483     }
12484 }
12485
12486 void
12487 EditCommentEvent()
12488 {
12489     char title[MSG_SIZ];
12490
12491     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12492       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12493     } else {
12494       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12495                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12496                parseList[currentMove - 1]);
12497     }
12498
12499     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12500 }
12501
12502
12503 void
12504 EditTagsEvent()
12505 {
12506     char *tags = PGNTags(&gameInfo);
12507     bookUp = FALSE;
12508     EditTagsPopUp(tags, NULL);
12509     free(tags);
12510 }
12511
12512 void
12513 AnalyzeModeEvent()
12514 {
12515     if (appData.noChessProgram || gameMode == AnalyzeMode)
12516       return;
12517
12518     if (gameMode != AnalyzeFile) {
12519         if (!appData.icsEngineAnalyze) {
12520                EditGameEvent();
12521                if (gameMode != EditGame) return;
12522         }
12523         ResurrectChessProgram();
12524         SendToProgram("analyze\n", &first);
12525         first.analyzing = TRUE;
12526         /*first.maybeThinking = TRUE;*/
12527         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12528         EngineOutputPopUp();
12529     }
12530     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12531     pausing = FALSE;
12532     ModeHighlight();
12533     SetGameInfo();
12534
12535     StartAnalysisClock();
12536     GetTimeMark(&lastNodeCountTime);
12537     lastNodeCount = 0;
12538 }
12539
12540 void
12541 AnalyzeFileEvent()
12542 {
12543     if (appData.noChessProgram || gameMode == AnalyzeFile)
12544       return;
12545
12546     if (gameMode != AnalyzeMode) {
12547         EditGameEvent();
12548         if (gameMode != EditGame) return;
12549         ResurrectChessProgram();
12550         SendToProgram("analyze\n", &first);
12551         first.analyzing = TRUE;
12552         /*first.maybeThinking = TRUE;*/
12553         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12554         EngineOutputPopUp();
12555     }
12556     gameMode = AnalyzeFile;
12557     pausing = FALSE;
12558     ModeHighlight();
12559     SetGameInfo();
12560
12561     StartAnalysisClock();
12562     GetTimeMark(&lastNodeCountTime);
12563     lastNodeCount = 0;
12564 }
12565
12566 void
12567 MachineWhiteEvent()
12568 {
12569     char buf[MSG_SIZ];
12570     char *bookHit = NULL;
12571
12572     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12573       return;
12574
12575
12576     if (gameMode == PlayFromGameFile ||
12577         gameMode == TwoMachinesPlay  ||
12578         gameMode == Training         ||
12579         gameMode == AnalyzeMode      ||
12580         gameMode == EndOfGame)
12581         EditGameEvent();
12582
12583     if (gameMode == EditPosition)
12584         EditPositionDone(TRUE);
12585
12586     if (!WhiteOnMove(currentMove)) {
12587         DisplayError(_("It is not White's turn"), 0);
12588         return;
12589     }
12590
12591     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12592       ExitAnalyzeMode();
12593
12594     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12595         gameMode == AnalyzeFile)
12596         TruncateGame();
12597
12598     ResurrectChessProgram();    /* in case it isn't running */
12599     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12600         gameMode = MachinePlaysWhite;
12601         ResetClocks();
12602     } else
12603     gameMode = MachinePlaysWhite;
12604     pausing = FALSE;
12605     ModeHighlight();
12606     SetGameInfo();
12607     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12608     DisplayTitle(buf);
12609     if (first.sendName) {
12610       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12611       SendToProgram(buf, &first);
12612     }
12613     if (first.sendTime) {
12614       if (first.useColors) {
12615         SendToProgram("black\n", &first); /*gnu kludge*/
12616       }
12617       SendTimeRemaining(&first, TRUE);
12618     }
12619     if (first.useColors) {
12620       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12621     }
12622     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12623     SetMachineThinkingEnables();
12624     first.maybeThinking = TRUE;
12625     StartClocks();
12626     firstMove = FALSE;
12627
12628     if (appData.autoFlipView && !flipView) {
12629       flipView = !flipView;
12630       DrawPosition(FALSE, NULL);
12631       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12632     }
12633
12634     if(bookHit) { // [HGM] book: simulate book reply
12635         static char bookMove[MSG_SIZ]; // a bit generous?
12636
12637         programStats.nodes = programStats.depth = programStats.time =
12638         programStats.score = programStats.got_only_move = 0;
12639         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12640
12641         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12642         strcat(bookMove, bookHit);
12643         HandleMachineMove(bookMove, &first);
12644     }
12645 }
12646
12647 void
12648 MachineBlackEvent()
12649 {
12650   char buf[MSG_SIZ];
12651   char *bookHit = NULL;
12652
12653     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12654         return;
12655
12656
12657     if (gameMode == PlayFromGameFile ||
12658         gameMode == TwoMachinesPlay  ||
12659         gameMode == Training         ||
12660         gameMode == AnalyzeMode      ||
12661         gameMode == EndOfGame)
12662         EditGameEvent();
12663
12664     if (gameMode == EditPosition)
12665         EditPositionDone(TRUE);
12666
12667     if (WhiteOnMove(currentMove)) {
12668         DisplayError(_("It is not Black's turn"), 0);
12669         return;
12670     }
12671
12672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12673       ExitAnalyzeMode();
12674
12675     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12676         gameMode == AnalyzeFile)
12677         TruncateGame();
12678
12679     ResurrectChessProgram();    /* in case it isn't running */
12680     gameMode = MachinePlaysBlack;
12681     pausing = FALSE;
12682     ModeHighlight();
12683     SetGameInfo();
12684     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12685     DisplayTitle(buf);
12686     if (first.sendName) {
12687       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12688       SendToProgram(buf, &first);
12689     }
12690     if (first.sendTime) {
12691       if (first.useColors) {
12692         SendToProgram("white\n", &first); /*gnu kludge*/
12693       }
12694       SendTimeRemaining(&first, FALSE);
12695     }
12696     if (first.useColors) {
12697       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12698     }
12699     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12700     SetMachineThinkingEnables();
12701     first.maybeThinking = TRUE;
12702     StartClocks();
12703
12704     if (appData.autoFlipView && flipView) {
12705       flipView = !flipView;
12706       DrawPosition(FALSE, NULL);
12707       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12708     }
12709     if(bookHit) { // [HGM] book: simulate book reply
12710         static char bookMove[MSG_SIZ]; // a bit generous?
12711
12712         programStats.nodes = programStats.depth = programStats.time =
12713         programStats.score = programStats.got_only_move = 0;
12714         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12715
12716         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12717         strcat(bookMove, bookHit);
12718         HandleMachineMove(bookMove, &first);
12719     }
12720 }
12721
12722
12723 void
12724 DisplayTwoMachinesTitle()
12725 {
12726     char buf[MSG_SIZ];
12727     if (appData.matchGames > 0) {
12728         if(appData.tourneyFile[0]) {
12729           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12730                    gameInfo.white, gameInfo.black,
12731                    nextGame+1, appData.matchGames+1,
12732                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12733         } else 
12734         if (first.twoMachinesColor[0] == 'w') {
12735           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12736                    gameInfo.white, gameInfo.black,
12737                    first.matchWins, second.matchWins,
12738                    matchGame - 1 - (first.matchWins + second.matchWins));
12739         } else {
12740           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12741                    gameInfo.white, gameInfo.black,
12742                    second.matchWins, first.matchWins,
12743                    matchGame - 1 - (first.matchWins + second.matchWins));
12744         }
12745     } else {
12746       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12747     }
12748     DisplayTitle(buf);
12749 }
12750
12751 void
12752 SettingsMenuIfReady()
12753 {
12754   if (second.lastPing != second.lastPong) {
12755     DisplayMessage("", _("Waiting for second chess program"));
12756     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12757     return;
12758   }
12759   ThawUI();
12760   DisplayMessage("", "");
12761   SettingsPopUp(&second);
12762 }
12763
12764 int
12765 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12766 {
12767     char buf[MSG_SIZ];
12768     if (cps->pr == NULL) {
12769         StartChessProgram(cps);
12770         if (cps->protocolVersion == 1) {
12771           retry();
12772         } else {
12773           /* kludge: allow timeout for initial "feature" command */
12774           FreezeUI();
12775           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12776           DisplayMessage("", buf);
12777           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12778         }
12779         return 1;
12780     }
12781     return 0;
12782 }
12783
12784 void
12785 TwoMachinesEvent P((void))
12786 {
12787     int i;
12788     char buf[MSG_SIZ];
12789     ChessProgramState *onmove;
12790     char *bookHit = NULL;
12791     static int stalling = 0;
12792     TimeMark now;
12793     long wait;
12794
12795     if (appData.noChessProgram) return;
12796
12797     switch (gameMode) {
12798       case TwoMachinesPlay:
12799         return;
12800       case MachinePlaysWhite:
12801       case MachinePlaysBlack:
12802         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12803             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12804             return;
12805         }
12806         /* fall through */
12807       case BeginningOfGame:
12808       case PlayFromGameFile:
12809       case EndOfGame:
12810         EditGameEvent();
12811         if (gameMode != EditGame) return;
12812         break;
12813       case EditPosition:
12814         EditPositionDone(TRUE);
12815         break;
12816       case AnalyzeMode:
12817       case AnalyzeFile:
12818         ExitAnalyzeMode();
12819         break;
12820       case EditGame:
12821       default:
12822         break;
12823     }
12824
12825 //    forwardMostMove = currentMove;
12826     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12827
12828     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12829
12830     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12831     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12832       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12833       return;
12834     }
12835     if(!stalling) {
12836       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12837       SendToProgram("force\n", &second);
12838       stalling = 1;
12839       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12840       return;
12841     }
12842     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12843     if(appData.matchPause>10000 || appData.matchPause<10)
12844                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12845     wait = SubtractTimeMarks(&now, &pauseStart);
12846     if(wait < appData.matchPause) {
12847         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12848         return;
12849     }
12850     stalling = 0;
12851     DisplayMessage("", "");
12852     if (startedFromSetupPosition) {
12853         SendBoard(&second, backwardMostMove);
12854     if (appData.debugMode) {
12855         fprintf(debugFP, "Two Machines\n");
12856     }
12857     }
12858     for (i = backwardMostMove; i < forwardMostMove; i++) {
12859         SendMoveToProgram(i, &second);
12860     }
12861
12862     gameMode = TwoMachinesPlay;
12863     pausing = FALSE;
12864     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12865     SetGameInfo();
12866     DisplayTwoMachinesTitle();
12867     firstMove = TRUE;
12868     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12869         onmove = &first;
12870     } else {
12871         onmove = &second;
12872     }
12873     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12874     SendToProgram(first.computerString, &first);
12875     if (first.sendName) {
12876       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12877       SendToProgram(buf, &first);
12878     }
12879     SendToProgram(second.computerString, &second);
12880     if (second.sendName) {
12881       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12882       SendToProgram(buf, &second);
12883     }
12884
12885     ResetClocks();
12886     if (!first.sendTime || !second.sendTime) {
12887         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12888         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12889     }
12890     if (onmove->sendTime) {
12891       if (onmove->useColors) {
12892         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12893       }
12894       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12895     }
12896     if (onmove->useColors) {
12897       SendToProgram(onmove->twoMachinesColor, onmove);
12898     }
12899     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12900 //    SendToProgram("go\n", onmove);
12901     onmove->maybeThinking = TRUE;
12902     SetMachineThinkingEnables();
12903
12904     StartClocks();
12905
12906     if(bookHit) { // [HGM] book: simulate book reply
12907         static char bookMove[MSG_SIZ]; // a bit generous?
12908
12909         programStats.nodes = programStats.depth = programStats.time =
12910         programStats.score = programStats.got_only_move = 0;
12911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12912
12913         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12914         strcat(bookMove, bookHit);
12915         savedMessage = bookMove; // args for deferred call
12916         savedState = onmove;
12917         ScheduleDelayedEvent(DeferredBookMove, 1);
12918     }
12919 }
12920
12921 void
12922 TrainingEvent()
12923 {
12924     if (gameMode == Training) {
12925       SetTrainingModeOff();
12926       gameMode = PlayFromGameFile;
12927       DisplayMessage("", _("Training mode off"));
12928     } else {
12929       gameMode = Training;
12930       animateTraining = appData.animate;
12931
12932       /* make sure we are not already at the end of the game */
12933       if (currentMove < forwardMostMove) {
12934         SetTrainingModeOn();
12935         DisplayMessage("", _("Training mode on"));
12936       } else {
12937         gameMode = PlayFromGameFile;
12938         DisplayError(_("Already at end of game"), 0);
12939       }
12940     }
12941     ModeHighlight();
12942 }
12943
12944 void
12945 IcsClientEvent()
12946 {
12947     if (!appData.icsActive) return;
12948     switch (gameMode) {
12949       case IcsPlayingWhite:
12950       case IcsPlayingBlack:
12951       case IcsObserving:
12952       case IcsIdle:
12953       case BeginningOfGame:
12954       case IcsExamining:
12955         return;
12956
12957       case EditGame:
12958         break;
12959
12960       case EditPosition:
12961         EditPositionDone(TRUE);
12962         break;
12963
12964       case AnalyzeMode:
12965       case AnalyzeFile:
12966         ExitAnalyzeMode();
12967         break;
12968
12969       default:
12970         EditGameEvent();
12971         break;
12972     }
12973
12974     gameMode = IcsIdle;
12975     ModeHighlight();
12976     return;
12977 }
12978
12979
12980 void
12981 EditGameEvent()
12982 {
12983     int i;
12984
12985     switch (gameMode) {
12986       case Training:
12987         SetTrainingModeOff();
12988         break;
12989       case MachinePlaysWhite:
12990       case MachinePlaysBlack:
12991       case BeginningOfGame:
12992         SendToProgram("force\n", &first);
12993         SetUserThinkingEnables();
12994         break;
12995       case PlayFromGameFile:
12996         (void) StopLoadGameTimer();
12997         if (gameFileFP != NULL) {
12998             gameFileFP = NULL;
12999         }
13000         break;
13001       case EditPosition:
13002         EditPositionDone(TRUE);
13003         break;
13004       case AnalyzeMode:
13005       case AnalyzeFile:
13006         ExitAnalyzeMode();
13007         SendToProgram("force\n", &first);
13008         break;
13009       case TwoMachinesPlay:
13010         GameEnds(EndOfFile, NULL, GE_PLAYER);
13011         ResurrectChessProgram();
13012         SetUserThinkingEnables();
13013         break;
13014       case EndOfGame:
13015         ResurrectChessProgram();
13016         break;
13017       case IcsPlayingBlack:
13018       case IcsPlayingWhite:
13019         DisplayError(_("Warning: You are still playing a game"), 0);
13020         break;
13021       case IcsObserving:
13022         DisplayError(_("Warning: You are still observing a game"), 0);
13023         break;
13024       case IcsExamining:
13025         DisplayError(_("Warning: You are still examining a game"), 0);
13026         break;
13027       case IcsIdle:
13028         break;
13029       case EditGame:
13030       default:
13031         return;
13032     }
13033
13034     pausing = FALSE;
13035     StopClocks();
13036     first.offeredDraw = second.offeredDraw = 0;
13037
13038     if (gameMode == PlayFromGameFile) {
13039         whiteTimeRemaining = timeRemaining[0][currentMove];
13040         blackTimeRemaining = timeRemaining[1][currentMove];
13041         DisplayTitle("");
13042     }
13043
13044     if (gameMode == MachinePlaysWhite ||
13045         gameMode == MachinePlaysBlack ||
13046         gameMode == TwoMachinesPlay ||
13047         gameMode == EndOfGame) {
13048         i = forwardMostMove;
13049         while (i > currentMove) {
13050             SendToProgram("undo\n", &first);
13051             i--;
13052         }
13053         whiteTimeRemaining = timeRemaining[0][currentMove];
13054         blackTimeRemaining = timeRemaining[1][currentMove];
13055         DisplayBothClocks();
13056         if (whiteFlag || blackFlag) {
13057             whiteFlag = blackFlag = 0;
13058         }
13059         DisplayTitle("");
13060     }
13061
13062     gameMode = EditGame;
13063     ModeHighlight();
13064     SetGameInfo();
13065 }
13066
13067
13068 void
13069 EditPositionEvent()
13070 {
13071     if (gameMode == EditPosition) {
13072         EditGameEvent();
13073         return;
13074     }
13075
13076     EditGameEvent();
13077     if (gameMode != EditGame) return;
13078
13079     gameMode = EditPosition;
13080     ModeHighlight();
13081     SetGameInfo();
13082     if (currentMove > 0)
13083       CopyBoard(boards[0], boards[currentMove]);
13084
13085     blackPlaysFirst = !WhiteOnMove(currentMove);
13086     ResetClocks();
13087     currentMove = forwardMostMove = backwardMostMove = 0;
13088     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13089     DisplayMove(-1);
13090 }
13091
13092 void
13093 ExitAnalyzeMode()
13094 {
13095     /* [DM] icsEngineAnalyze - possible call from other functions */
13096     if (appData.icsEngineAnalyze) {
13097         appData.icsEngineAnalyze = FALSE;
13098
13099         DisplayMessage("",_("Close ICS engine analyze..."));
13100     }
13101     if (first.analysisSupport && first.analyzing) {
13102       SendToProgram("exit\n", &first);
13103       first.analyzing = FALSE;
13104     }
13105     thinkOutput[0] = NULLCHAR;
13106 }
13107
13108 void
13109 EditPositionDone(Boolean fakeRights)
13110 {
13111     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13112
13113     startedFromSetupPosition = TRUE;
13114     InitChessProgram(&first, FALSE);
13115     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13116       boards[0][EP_STATUS] = EP_NONE;
13117       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13118     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13119         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13120         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13121       } else boards[0][CASTLING][2] = NoRights;
13122     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13123         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13124         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13125       } else boards[0][CASTLING][5] = NoRights;
13126     }
13127     SendToProgram("force\n", &first);
13128     if (blackPlaysFirst) {
13129         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13130         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13131         currentMove = forwardMostMove = backwardMostMove = 1;
13132         CopyBoard(boards[1], boards[0]);
13133     } else {
13134         currentMove = forwardMostMove = backwardMostMove = 0;
13135     }
13136     SendBoard(&first, forwardMostMove);
13137     if (appData.debugMode) {
13138         fprintf(debugFP, "EditPosDone\n");
13139     }
13140     DisplayTitle("");
13141     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13142     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13143     gameMode = EditGame;
13144     ModeHighlight();
13145     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13146     ClearHighlights(); /* [AS] */
13147 }
13148
13149 /* Pause for `ms' milliseconds */
13150 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13151 void
13152 TimeDelay(ms)
13153      long ms;
13154 {
13155     TimeMark m1, m2;
13156
13157     GetTimeMark(&m1);
13158     do {
13159         GetTimeMark(&m2);
13160     } while (SubtractTimeMarks(&m2, &m1) < ms);
13161 }
13162
13163 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13164 void
13165 SendMultiLineToICS(buf)
13166      char *buf;
13167 {
13168     char temp[MSG_SIZ+1], *p;
13169     int len;
13170
13171     len = strlen(buf);
13172     if (len > MSG_SIZ)
13173       len = MSG_SIZ;
13174
13175     strncpy(temp, buf, len);
13176     temp[len] = 0;
13177
13178     p = temp;
13179     while (*p) {
13180         if (*p == '\n' || *p == '\r')
13181           *p = ' ';
13182         ++p;
13183     }
13184
13185     strcat(temp, "\n");
13186     SendToICS(temp);
13187     SendToPlayer(temp, strlen(temp));
13188 }
13189
13190 void
13191 SetWhiteToPlayEvent()
13192 {
13193     if (gameMode == EditPosition) {
13194         blackPlaysFirst = FALSE;
13195         DisplayBothClocks();    /* works because currentMove is 0 */
13196     } else if (gameMode == IcsExamining) {
13197         SendToICS(ics_prefix);
13198         SendToICS("tomove white\n");
13199     }
13200 }
13201
13202 void
13203 SetBlackToPlayEvent()
13204 {
13205     if (gameMode == EditPosition) {
13206         blackPlaysFirst = TRUE;
13207         currentMove = 1;        /* kludge */
13208         DisplayBothClocks();
13209         currentMove = 0;
13210     } else if (gameMode == IcsExamining) {
13211         SendToICS(ics_prefix);
13212         SendToICS("tomove black\n");
13213     }
13214 }
13215
13216 void
13217 EditPositionMenuEvent(selection, x, y)
13218      ChessSquare selection;
13219      int x, y;
13220 {
13221     char buf[MSG_SIZ];
13222     ChessSquare piece = boards[0][y][x];
13223
13224     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13225
13226     switch (selection) {
13227       case ClearBoard:
13228         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13229             SendToICS(ics_prefix);
13230             SendToICS("bsetup clear\n");
13231         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13232             SendToICS(ics_prefix);
13233             SendToICS("clearboard\n");
13234         } else {
13235             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13236                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13237                 for (y = 0; y < BOARD_HEIGHT; y++) {
13238                     if (gameMode == IcsExamining) {
13239                         if (boards[currentMove][y][x] != EmptySquare) {
13240                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13241                                     AAA + x, ONE + y);
13242                             SendToICS(buf);
13243                         }
13244                     } else {
13245                         boards[0][y][x] = p;
13246                     }
13247                 }
13248             }
13249         }
13250         if (gameMode == EditPosition) {
13251             DrawPosition(FALSE, boards[0]);
13252         }
13253         break;
13254
13255       case WhitePlay:
13256         SetWhiteToPlayEvent();
13257         break;
13258
13259       case BlackPlay:
13260         SetBlackToPlayEvent();
13261         break;
13262
13263       case EmptySquare:
13264         if (gameMode == IcsExamining) {
13265             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13266             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13267             SendToICS(buf);
13268         } else {
13269             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13270                 if(x == BOARD_LEFT-2) {
13271                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13272                     boards[0][y][1] = 0;
13273                 } else
13274                 if(x == BOARD_RGHT+1) {
13275                     if(y >= gameInfo.holdingsSize) break;
13276                     boards[0][y][BOARD_WIDTH-2] = 0;
13277                 } else break;
13278             }
13279             boards[0][y][x] = EmptySquare;
13280             DrawPosition(FALSE, boards[0]);
13281         }
13282         break;
13283
13284       case PromotePiece:
13285         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13286            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13287             selection = (ChessSquare) (PROMOTED piece);
13288         } else if(piece == EmptySquare) selection = WhiteSilver;
13289         else selection = (ChessSquare)((int)piece - 1);
13290         goto defaultlabel;
13291
13292       case DemotePiece:
13293         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13294            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13295             selection = (ChessSquare) (DEMOTED piece);
13296         } else if(piece == EmptySquare) selection = BlackSilver;
13297         else selection = (ChessSquare)((int)piece + 1);
13298         goto defaultlabel;
13299
13300       case WhiteQueen:
13301       case BlackQueen:
13302         if(gameInfo.variant == VariantShatranj ||
13303            gameInfo.variant == VariantXiangqi  ||
13304            gameInfo.variant == VariantCourier  ||
13305            gameInfo.variant == VariantMakruk     )
13306             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13307         goto defaultlabel;
13308
13309       case WhiteKing:
13310       case BlackKing:
13311         if(gameInfo.variant == VariantXiangqi)
13312             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13313         if(gameInfo.variant == VariantKnightmate)
13314             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13315       default:
13316         defaultlabel:
13317         if (gameMode == IcsExamining) {
13318             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13319             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13320                      PieceToChar(selection), AAA + x, ONE + y);
13321             SendToICS(buf);
13322         } else {
13323             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13324                 int n;
13325                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13326                     n = PieceToNumber(selection - BlackPawn);
13327                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13328                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13329                     boards[0][BOARD_HEIGHT-1-n][1]++;
13330                 } else
13331                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13332                     n = PieceToNumber(selection);
13333                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13334                     boards[0][n][BOARD_WIDTH-1] = selection;
13335                     boards[0][n][BOARD_WIDTH-2]++;
13336                 }
13337             } else
13338             boards[0][y][x] = selection;
13339             DrawPosition(TRUE, boards[0]);
13340         }
13341         break;
13342     }
13343 }
13344
13345
13346 void
13347 DropMenuEvent(selection, x, y)
13348      ChessSquare selection;
13349      int x, y;
13350 {
13351     ChessMove moveType;
13352
13353     switch (gameMode) {
13354       case IcsPlayingWhite:
13355       case MachinePlaysBlack:
13356         if (!WhiteOnMove(currentMove)) {
13357             DisplayMoveError(_("It is Black's turn"));
13358             return;
13359         }
13360         moveType = WhiteDrop;
13361         break;
13362       case IcsPlayingBlack:
13363       case MachinePlaysWhite:
13364         if (WhiteOnMove(currentMove)) {
13365             DisplayMoveError(_("It is White's turn"));
13366             return;
13367         }
13368         moveType = BlackDrop;
13369         break;
13370       case EditGame:
13371         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13372         break;
13373       default:
13374         return;
13375     }
13376
13377     if (moveType == BlackDrop && selection < BlackPawn) {
13378       selection = (ChessSquare) ((int) selection
13379                                  + (int) BlackPawn - (int) WhitePawn);
13380     }
13381     if (boards[currentMove][y][x] != EmptySquare) {
13382         DisplayMoveError(_("That square is occupied"));
13383         return;
13384     }
13385
13386     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13387 }
13388
13389 void
13390 AcceptEvent()
13391 {
13392     /* Accept a pending offer of any kind from opponent */
13393
13394     if (appData.icsActive) {
13395         SendToICS(ics_prefix);
13396         SendToICS("accept\n");
13397     } else if (cmailMsgLoaded) {
13398         if (currentMove == cmailOldMove &&
13399             commentList[cmailOldMove] != NULL &&
13400             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13401                    "Black offers a draw" : "White offers a draw")) {
13402             TruncateGame();
13403             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13404             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13405         } else {
13406             DisplayError(_("There is no pending offer on this move"), 0);
13407             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13408         }
13409     } else {
13410         /* Not used for offers from chess program */
13411     }
13412 }
13413
13414 void
13415 DeclineEvent()
13416 {
13417     /* Decline a pending offer of any kind from opponent */
13418
13419     if (appData.icsActive) {
13420         SendToICS(ics_prefix);
13421         SendToICS("decline\n");
13422     } else if (cmailMsgLoaded) {
13423         if (currentMove == cmailOldMove &&
13424             commentList[cmailOldMove] != NULL &&
13425             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13426                    "Black offers a draw" : "White offers a draw")) {
13427 #ifdef NOTDEF
13428             AppendComment(cmailOldMove, "Draw declined", TRUE);
13429             DisplayComment(cmailOldMove - 1, "Draw declined");
13430 #endif /*NOTDEF*/
13431         } else {
13432             DisplayError(_("There is no pending offer on this move"), 0);
13433         }
13434     } else {
13435         /* Not used for offers from chess program */
13436     }
13437 }
13438
13439 void
13440 RematchEvent()
13441 {
13442     /* Issue ICS rematch command */
13443     if (appData.icsActive) {
13444         SendToICS(ics_prefix);
13445         SendToICS("rematch\n");
13446     }
13447 }
13448
13449 void
13450 CallFlagEvent()
13451 {
13452     /* Call your opponent's flag (claim a win on time) */
13453     if (appData.icsActive) {
13454         SendToICS(ics_prefix);
13455         SendToICS("flag\n");
13456     } else {
13457         switch (gameMode) {
13458           default:
13459             return;
13460           case MachinePlaysWhite:
13461             if (whiteFlag) {
13462                 if (blackFlag)
13463                   GameEnds(GameIsDrawn, "Both players ran out of time",
13464                            GE_PLAYER);
13465                 else
13466                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13467             } else {
13468                 DisplayError(_("Your opponent is not out of time"), 0);
13469             }
13470             break;
13471           case MachinePlaysBlack:
13472             if (blackFlag) {
13473                 if (whiteFlag)
13474                   GameEnds(GameIsDrawn, "Both players ran out of time",
13475                            GE_PLAYER);
13476                 else
13477                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13478             } else {
13479                 DisplayError(_("Your opponent is not out of time"), 0);
13480             }
13481             break;
13482         }
13483     }
13484 }
13485
13486 void
13487 ClockClick(int which)
13488 {       // [HGM] code moved to back-end from winboard.c
13489         if(which) { // black clock
13490           if (gameMode == EditPosition || gameMode == IcsExamining) {
13491             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13492             SetBlackToPlayEvent();
13493           } else if (gameMode == EditGame || shiftKey) {
13494             AdjustClock(which, -1);
13495           } else if (gameMode == IcsPlayingWhite ||
13496                      gameMode == MachinePlaysBlack) {
13497             CallFlagEvent();
13498           }
13499         } else { // white clock
13500           if (gameMode == EditPosition || gameMode == IcsExamining) {
13501             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13502             SetWhiteToPlayEvent();
13503           } else if (gameMode == EditGame || shiftKey) {
13504             AdjustClock(which, -1);
13505           } else if (gameMode == IcsPlayingBlack ||
13506                    gameMode == MachinePlaysWhite) {
13507             CallFlagEvent();
13508           }
13509         }
13510 }
13511
13512 void
13513 DrawEvent()
13514 {
13515     /* Offer draw or accept pending draw offer from opponent */
13516
13517     if (appData.icsActive) {
13518         /* Note: tournament rules require draw offers to be
13519            made after you make your move but before you punch
13520            your clock.  Currently ICS doesn't let you do that;
13521            instead, you immediately punch your clock after making
13522            a move, but you can offer a draw at any time. */
13523
13524         SendToICS(ics_prefix);
13525         SendToICS("draw\n");
13526         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13527     } else if (cmailMsgLoaded) {
13528         if (currentMove == cmailOldMove &&
13529             commentList[cmailOldMove] != NULL &&
13530             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13531                    "Black offers a draw" : "White offers a draw")) {
13532             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13533             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13534         } else if (currentMove == cmailOldMove + 1) {
13535             char *offer = WhiteOnMove(cmailOldMove) ?
13536               "White offers a draw" : "Black offers a draw";
13537             AppendComment(currentMove, offer, TRUE);
13538             DisplayComment(currentMove - 1, offer);
13539             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13540         } else {
13541             DisplayError(_("You must make your move before offering a draw"), 0);
13542             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13543         }
13544     } else if (first.offeredDraw) {
13545         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13546     } else {
13547         if (first.sendDrawOffers) {
13548             SendToProgram("draw\n", &first);
13549             userOfferedDraw = TRUE;
13550         }
13551     }
13552 }
13553
13554 void
13555 AdjournEvent()
13556 {
13557     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13558
13559     if (appData.icsActive) {
13560         SendToICS(ics_prefix);
13561         SendToICS("adjourn\n");
13562     } else {
13563         /* Currently GNU Chess doesn't offer or accept Adjourns */
13564     }
13565 }
13566
13567
13568 void
13569 AbortEvent()
13570 {
13571     /* Offer Abort or accept pending Abort offer from opponent */
13572
13573     if (appData.icsActive) {
13574         SendToICS(ics_prefix);
13575         SendToICS("abort\n");
13576     } else {
13577         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13578     }
13579 }
13580
13581 void
13582 ResignEvent()
13583 {
13584     /* Resign.  You can do this even if it's not your turn. */
13585
13586     if (appData.icsActive) {
13587         SendToICS(ics_prefix);
13588         SendToICS("resign\n");
13589     } else {
13590         switch (gameMode) {
13591           case MachinePlaysWhite:
13592             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13593             break;
13594           case MachinePlaysBlack:
13595             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13596             break;
13597           case EditGame:
13598             if (cmailMsgLoaded) {
13599                 TruncateGame();
13600                 if (WhiteOnMove(cmailOldMove)) {
13601                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13602                 } else {
13603                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13604                 }
13605                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13606             }
13607             break;
13608           default:
13609             break;
13610         }
13611     }
13612 }
13613
13614
13615 void
13616 StopObservingEvent()
13617 {
13618     /* Stop observing current games */
13619     SendToICS(ics_prefix);
13620     SendToICS("unobserve\n");
13621 }
13622
13623 void
13624 StopExaminingEvent()
13625 {
13626     /* Stop observing current game */
13627     SendToICS(ics_prefix);
13628     SendToICS("unexamine\n");
13629 }
13630
13631 void
13632 ForwardInner(target)
13633      int target;
13634 {
13635     int limit;
13636
13637     if (appData.debugMode)
13638         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13639                 target, currentMove, forwardMostMove);
13640
13641     if (gameMode == EditPosition)
13642       return;
13643
13644     if (gameMode == PlayFromGameFile && !pausing)
13645       PauseEvent();
13646
13647     if (gameMode == IcsExamining && pausing)
13648       limit = pauseExamForwardMostMove;
13649     else
13650       limit = forwardMostMove;
13651
13652     if (target > limit) target = limit;
13653
13654     if (target > 0 && moveList[target - 1][0]) {
13655         int fromX, fromY, toX, toY;
13656         toX = moveList[target - 1][2] - AAA;
13657         toY = moveList[target - 1][3] - ONE;
13658         if (moveList[target - 1][1] == '@') {
13659             if (appData.highlightLastMove) {
13660                 SetHighlights(-1, -1, toX, toY);
13661             }
13662         } else {
13663             fromX = moveList[target - 1][0] - AAA;
13664             fromY = moveList[target - 1][1] - ONE;
13665             if (target == currentMove + 1) {
13666                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13667             }
13668             if (appData.highlightLastMove) {
13669                 SetHighlights(fromX, fromY, toX, toY);
13670             }
13671         }
13672     }
13673     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13674         gameMode == Training || gameMode == PlayFromGameFile ||
13675         gameMode == AnalyzeFile) {
13676         while (currentMove < target) {
13677             SendMoveToProgram(currentMove++, &first);
13678         }
13679     } else {
13680         currentMove = target;
13681     }
13682
13683     if (gameMode == EditGame || gameMode == EndOfGame) {
13684         whiteTimeRemaining = timeRemaining[0][currentMove];
13685         blackTimeRemaining = timeRemaining[1][currentMove];
13686     }
13687     DisplayBothClocks();
13688     DisplayMove(currentMove - 1);
13689     DrawPosition(FALSE, boards[currentMove]);
13690     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13691     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13692         DisplayComment(currentMove - 1, commentList[currentMove]);
13693     }
13694     DisplayBook(currentMove);
13695 }
13696
13697
13698 void
13699 ForwardEvent()
13700 {
13701     if (gameMode == IcsExamining && !pausing) {
13702         SendToICS(ics_prefix);
13703         SendToICS("forward\n");
13704     } else {
13705         ForwardInner(currentMove + 1);
13706     }
13707 }
13708
13709 void
13710 ToEndEvent()
13711 {
13712     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13713         /* to optimze, we temporarily turn off analysis mode while we feed
13714          * the remaining moves to the engine. Otherwise we get analysis output
13715          * after each move.
13716          */
13717         if (first.analysisSupport) {
13718           SendToProgram("exit\nforce\n", &first);
13719           first.analyzing = FALSE;
13720         }
13721     }
13722
13723     if (gameMode == IcsExamining && !pausing) {
13724         SendToICS(ics_prefix);
13725         SendToICS("forward 999999\n");
13726     } else {
13727         ForwardInner(forwardMostMove);
13728     }
13729
13730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13731         /* we have fed all the moves, so reactivate analysis mode */
13732         SendToProgram("analyze\n", &first);
13733         first.analyzing = TRUE;
13734         /*first.maybeThinking = TRUE;*/
13735         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13736     }
13737 }
13738
13739 void
13740 BackwardInner(target)
13741      int target;
13742 {
13743     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13744
13745     if (appData.debugMode)
13746         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13747                 target, currentMove, forwardMostMove);
13748
13749     if (gameMode == EditPosition) return;
13750     if (currentMove <= backwardMostMove) {
13751         ClearHighlights();
13752         DrawPosition(full_redraw, boards[currentMove]);
13753         return;
13754     }
13755     if (gameMode == PlayFromGameFile && !pausing)
13756       PauseEvent();
13757
13758     if (moveList[target][0]) {
13759         int fromX, fromY, toX, toY;
13760         toX = moveList[target][2] - AAA;
13761         toY = moveList[target][3] - ONE;
13762         if (moveList[target][1] == '@') {
13763             if (appData.highlightLastMove) {
13764                 SetHighlights(-1, -1, toX, toY);
13765             }
13766         } else {
13767             fromX = moveList[target][0] - AAA;
13768             fromY = moveList[target][1] - ONE;
13769             if (target == currentMove - 1) {
13770                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13771             }
13772             if (appData.highlightLastMove) {
13773                 SetHighlights(fromX, fromY, toX, toY);
13774             }
13775         }
13776     }
13777     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13778         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13779         while (currentMove > target) {
13780             SendToProgram("undo\n", &first);
13781             currentMove--;
13782         }
13783     } else {
13784         currentMove = target;
13785     }
13786
13787     if (gameMode == EditGame || gameMode == EndOfGame) {
13788         whiteTimeRemaining = timeRemaining[0][currentMove];
13789         blackTimeRemaining = timeRemaining[1][currentMove];
13790     }
13791     DisplayBothClocks();
13792     DisplayMove(currentMove - 1);
13793     DrawPosition(full_redraw, boards[currentMove]);
13794     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13795     // [HGM] PV info: routine tests if comment empty
13796     DisplayComment(currentMove - 1, commentList[currentMove]);
13797     DisplayBook(currentMove);
13798 }
13799
13800 void
13801 BackwardEvent()
13802 {
13803     if (gameMode == IcsExamining && !pausing) {
13804         SendToICS(ics_prefix);
13805         SendToICS("backward\n");
13806     } else {
13807         BackwardInner(currentMove - 1);
13808     }
13809 }
13810
13811 void
13812 ToStartEvent()
13813 {
13814     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13815         /* to optimize, we temporarily turn off analysis mode while we undo
13816          * all the moves. Otherwise we get analysis output after each undo.
13817          */
13818         if (first.analysisSupport) {
13819           SendToProgram("exit\nforce\n", &first);
13820           first.analyzing = FALSE;
13821         }
13822     }
13823
13824     if (gameMode == IcsExamining && !pausing) {
13825         SendToICS(ics_prefix);
13826         SendToICS("backward 999999\n");
13827     } else {
13828         BackwardInner(backwardMostMove);
13829     }
13830
13831     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13832         /* we have fed all the moves, so reactivate analysis mode */
13833         SendToProgram("analyze\n", &first);
13834         first.analyzing = TRUE;
13835         /*first.maybeThinking = TRUE;*/
13836         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13837     }
13838 }
13839
13840 void
13841 ToNrEvent(int to)
13842 {
13843   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13844   if (to >= forwardMostMove) to = forwardMostMove;
13845   if (to <= backwardMostMove) to = backwardMostMove;
13846   if (to < currentMove) {
13847     BackwardInner(to);
13848   } else {
13849     ForwardInner(to);
13850   }
13851 }
13852
13853 void
13854 RevertEvent(Boolean annotate)
13855 {
13856     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13857         return;
13858     }
13859     if (gameMode != IcsExamining) {
13860         DisplayError(_("You are not examining a game"), 0);
13861         return;
13862     }
13863     if (pausing) {
13864         DisplayError(_("You can't revert while pausing"), 0);
13865         return;
13866     }
13867     SendToICS(ics_prefix);
13868     SendToICS("revert\n");
13869 }
13870
13871 void
13872 RetractMoveEvent()
13873 {
13874     switch (gameMode) {
13875       case MachinePlaysWhite:
13876       case MachinePlaysBlack:
13877         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13878             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13879             return;
13880         }
13881         if (forwardMostMove < 2) return;
13882         currentMove = forwardMostMove = forwardMostMove - 2;
13883         whiteTimeRemaining = timeRemaining[0][currentMove];
13884         blackTimeRemaining = timeRemaining[1][currentMove];
13885         DisplayBothClocks();
13886         DisplayMove(currentMove - 1);
13887         ClearHighlights();/*!! could figure this out*/
13888         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13889         SendToProgram("remove\n", &first);
13890         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13891         break;
13892
13893       case BeginningOfGame:
13894       default:
13895         break;
13896
13897       case IcsPlayingWhite:
13898       case IcsPlayingBlack:
13899         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13900             SendToICS(ics_prefix);
13901             SendToICS("takeback 2\n");
13902         } else {
13903             SendToICS(ics_prefix);
13904             SendToICS("takeback 1\n");
13905         }
13906         break;
13907     }
13908 }
13909
13910 void
13911 MoveNowEvent()
13912 {
13913     ChessProgramState *cps;
13914
13915     switch (gameMode) {
13916       case MachinePlaysWhite:
13917         if (!WhiteOnMove(forwardMostMove)) {
13918             DisplayError(_("It is your turn"), 0);
13919             return;
13920         }
13921         cps = &first;
13922         break;
13923       case MachinePlaysBlack:
13924         if (WhiteOnMove(forwardMostMove)) {
13925             DisplayError(_("It is your turn"), 0);
13926             return;
13927         }
13928         cps = &first;
13929         break;
13930       case TwoMachinesPlay:
13931         if (WhiteOnMove(forwardMostMove) ==
13932             (first.twoMachinesColor[0] == 'w')) {
13933             cps = &first;
13934         } else {
13935             cps = &second;
13936         }
13937         break;
13938       case BeginningOfGame:
13939       default:
13940         return;
13941     }
13942     SendToProgram("?\n", cps);
13943 }
13944
13945 void
13946 TruncateGameEvent()
13947 {
13948     EditGameEvent();
13949     if (gameMode != EditGame) return;
13950     TruncateGame();
13951 }
13952
13953 void
13954 TruncateGame()
13955 {
13956     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13957     if (forwardMostMove > currentMove) {
13958         if (gameInfo.resultDetails != NULL) {
13959             free(gameInfo.resultDetails);
13960             gameInfo.resultDetails = NULL;
13961             gameInfo.result = GameUnfinished;
13962         }
13963         forwardMostMove = currentMove;
13964         HistorySet(parseList, backwardMostMove, forwardMostMove,
13965                    currentMove-1);
13966     }
13967 }
13968
13969 void
13970 HintEvent()
13971 {
13972     if (appData.noChessProgram) return;
13973     switch (gameMode) {
13974       case MachinePlaysWhite:
13975         if (WhiteOnMove(forwardMostMove)) {
13976             DisplayError(_("Wait until your turn"), 0);
13977             return;
13978         }
13979         break;
13980       case BeginningOfGame:
13981       case MachinePlaysBlack:
13982         if (!WhiteOnMove(forwardMostMove)) {
13983             DisplayError(_("Wait until your turn"), 0);
13984             return;
13985         }
13986         break;
13987       default:
13988         DisplayError(_("No hint available"), 0);
13989         return;
13990     }
13991     SendToProgram("hint\n", &first);
13992     hintRequested = TRUE;
13993 }
13994
13995 void
13996 BookEvent()
13997 {
13998     if (appData.noChessProgram) return;
13999     switch (gameMode) {
14000       case MachinePlaysWhite:
14001         if (WhiteOnMove(forwardMostMove)) {
14002             DisplayError(_("Wait until your turn"), 0);
14003             return;
14004         }
14005         break;
14006       case BeginningOfGame:
14007       case MachinePlaysBlack:
14008         if (!WhiteOnMove(forwardMostMove)) {
14009             DisplayError(_("Wait until your turn"), 0);
14010             return;
14011         }
14012         break;
14013       case EditPosition:
14014         EditPositionDone(TRUE);
14015         break;
14016       case TwoMachinesPlay:
14017         return;
14018       default:
14019         break;
14020     }
14021     SendToProgram("bk\n", &first);
14022     bookOutput[0] = NULLCHAR;
14023     bookRequested = TRUE;
14024 }
14025
14026 void
14027 AboutGameEvent()
14028 {
14029     char *tags = PGNTags(&gameInfo);
14030     TagsPopUp(tags, CmailMsg());
14031     free(tags);
14032 }
14033
14034 /* end button procedures */
14035
14036 void
14037 PrintPosition(fp, move)
14038      FILE *fp;
14039      int move;
14040 {
14041     int i, j;
14042
14043     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14044         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14045             char c = PieceToChar(boards[move][i][j]);
14046             fputc(c == 'x' ? '.' : c, fp);
14047             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14048         }
14049     }
14050     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14051       fprintf(fp, "white to play\n");
14052     else
14053       fprintf(fp, "black to play\n");
14054 }
14055
14056 void
14057 PrintOpponents(fp)
14058      FILE *fp;
14059 {
14060     if (gameInfo.white != NULL) {
14061         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14062     } else {
14063         fprintf(fp, "\n");
14064     }
14065 }
14066
14067 /* Find last component of program's own name, using some heuristics */
14068 void
14069 TidyProgramName(prog, host, buf)
14070      char *prog, *host, buf[MSG_SIZ];
14071 {
14072     char *p, *q;
14073     int local = (strcmp(host, "localhost") == 0);
14074     while (!local && (p = strchr(prog, ';')) != NULL) {
14075         p++;
14076         while (*p == ' ') p++;
14077         prog = p;
14078     }
14079     if (*prog == '"' || *prog == '\'') {
14080         q = strchr(prog + 1, *prog);
14081     } else {
14082         q = strchr(prog, ' ');
14083     }
14084     if (q == NULL) q = prog + strlen(prog);
14085     p = q;
14086     while (p >= prog && *p != '/' && *p != '\\') p--;
14087     p++;
14088     if(p == prog && *p == '"') p++;
14089     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14090     memcpy(buf, p, q - p);
14091     buf[q - p] = NULLCHAR;
14092     if (!local) {
14093         strcat(buf, "@");
14094         strcat(buf, host);
14095     }
14096 }
14097
14098 char *
14099 TimeControlTagValue()
14100 {
14101     char buf[MSG_SIZ];
14102     if (!appData.clockMode) {
14103       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14104     } else if (movesPerSession > 0) {
14105       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14106     } else if (timeIncrement == 0) {
14107       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14108     } else {
14109       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14110     }
14111     return StrSave(buf);
14112 }
14113
14114 void
14115 SetGameInfo()
14116 {
14117     /* This routine is used only for certain modes */
14118     VariantClass v = gameInfo.variant;
14119     ChessMove r = GameUnfinished;
14120     char *p = NULL;
14121
14122     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14123         r = gameInfo.result;
14124         p = gameInfo.resultDetails;
14125         gameInfo.resultDetails = NULL;
14126     }
14127     ClearGameInfo(&gameInfo);
14128     gameInfo.variant = v;
14129
14130     switch (gameMode) {
14131       case MachinePlaysWhite:
14132         gameInfo.event = StrSave( appData.pgnEventHeader );
14133         gameInfo.site = StrSave(HostName());
14134         gameInfo.date = PGNDate();
14135         gameInfo.round = StrSave("-");
14136         gameInfo.white = StrSave(first.tidy);
14137         gameInfo.black = StrSave(UserName());
14138         gameInfo.timeControl = TimeControlTagValue();
14139         break;
14140
14141       case MachinePlaysBlack:
14142         gameInfo.event = StrSave( appData.pgnEventHeader );
14143         gameInfo.site = StrSave(HostName());
14144         gameInfo.date = PGNDate();
14145         gameInfo.round = StrSave("-");
14146         gameInfo.white = StrSave(UserName());
14147         gameInfo.black = StrSave(first.tidy);
14148         gameInfo.timeControl = TimeControlTagValue();
14149         break;
14150
14151       case TwoMachinesPlay:
14152         gameInfo.event = StrSave( appData.pgnEventHeader );
14153         gameInfo.site = StrSave(HostName());
14154         gameInfo.date = PGNDate();
14155         if (roundNr > 0) {
14156             char buf[MSG_SIZ];
14157             snprintf(buf, MSG_SIZ, "%d", roundNr);
14158             gameInfo.round = StrSave(buf);
14159         } else {
14160             gameInfo.round = StrSave("-");
14161         }
14162         if (first.twoMachinesColor[0] == 'w') {
14163             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14164             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14165         } else {
14166             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14167             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14168         }
14169         gameInfo.timeControl = TimeControlTagValue();
14170         break;
14171
14172       case EditGame:
14173         gameInfo.event = StrSave("Edited game");
14174         gameInfo.site = StrSave(HostName());
14175         gameInfo.date = PGNDate();
14176         gameInfo.round = StrSave("-");
14177         gameInfo.white = StrSave("-");
14178         gameInfo.black = StrSave("-");
14179         gameInfo.result = r;
14180         gameInfo.resultDetails = p;
14181         break;
14182
14183       case EditPosition:
14184         gameInfo.event = StrSave("Edited position");
14185         gameInfo.site = StrSave(HostName());
14186         gameInfo.date = PGNDate();
14187         gameInfo.round = StrSave("-");
14188         gameInfo.white = StrSave("-");
14189         gameInfo.black = StrSave("-");
14190         break;
14191
14192       case IcsPlayingWhite:
14193       case IcsPlayingBlack:
14194       case IcsObserving:
14195       case IcsExamining:
14196         break;
14197
14198       case PlayFromGameFile:
14199         gameInfo.event = StrSave("Game from non-PGN file");
14200         gameInfo.site = StrSave(HostName());
14201         gameInfo.date = PGNDate();
14202         gameInfo.round = StrSave("-");
14203         gameInfo.white = StrSave("?");
14204         gameInfo.black = StrSave("?");
14205         break;
14206
14207       default:
14208         break;
14209     }
14210 }
14211
14212 void
14213 ReplaceComment(index, text)
14214      int index;
14215      char *text;
14216 {
14217     int len;
14218     char *p;
14219     float score;
14220
14221     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14222        pvInfoList[index-1].depth == len &&
14223        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14224        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14225     while (*text == '\n') text++;
14226     len = strlen(text);
14227     while (len > 0 && text[len - 1] == '\n') len--;
14228
14229     if (commentList[index] != NULL)
14230       free(commentList[index]);
14231
14232     if (len == 0) {
14233         commentList[index] = NULL;
14234         return;
14235     }
14236   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14237       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14238       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14239     commentList[index] = (char *) malloc(len + 2);
14240     strncpy(commentList[index], text, len);
14241     commentList[index][len] = '\n';
14242     commentList[index][len + 1] = NULLCHAR;
14243   } else {
14244     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14245     char *p;
14246     commentList[index] = (char *) malloc(len + 7);
14247     safeStrCpy(commentList[index], "{\n", 3);
14248     safeStrCpy(commentList[index]+2, text, len+1);
14249     commentList[index][len+2] = NULLCHAR;
14250     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14251     strcat(commentList[index], "\n}\n");
14252   }
14253 }
14254
14255 void
14256 CrushCRs(text)
14257      char *text;
14258 {
14259   char *p = text;
14260   char *q = text;
14261   char ch;
14262
14263   do {
14264     ch = *p++;
14265     if (ch == '\r') continue;
14266     *q++ = ch;
14267   } while (ch != '\0');
14268 }
14269
14270 void
14271 AppendComment(index, text, addBraces)
14272      int index;
14273      char *text;
14274      Boolean addBraces; // [HGM] braces: tells if we should add {}
14275 {
14276     int oldlen, len;
14277     char *old;
14278
14279 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14280     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14281
14282     CrushCRs(text);
14283     while (*text == '\n') text++;
14284     len = strlen(text);
14285     while (len > 0 && text[len - 1] == '\n') len--;
14286
14287     if (len == 0) return;
14288
14289     if (commentList[index] != NULL) {
14290         old = commentList[index];
14291         oldlen = strlen(old);
14292         while(commentList[index][oldlen-1] ==  '\n')
14293           commentList[index][--oldlen] = NULLCHAR;
14294         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14295         safeStrCpy(commentList[index], old, oldlen + len + 6);
14296         free(old);
14297         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14298         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14299           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14300           while (*text == '\n') { text++; len--; }
14301           commentList[index][--oldlen] = NULLCHAR;
14302       }
14303         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14304         else          strcat(commentList[index], "\n");
14305         strcat(commentList[index], text);
14306         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14307         else          strcat(commentList[index], "\n");
14308     } else {
14309         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14310         if(addBraces)
14311           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14312         else commentList[index][0] = NULLCHAR;
14313         strcat(commentList[index], text);
14314         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14315         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14316     }
14317 }
14318
14319 static char * FindStr( char * text, char * sub_text )
14320 {
14321     char * result = strstr( text, sub_text );
14322
14323     if( result != NULL ) {
14324         result += strlen( sub_text );
14325     }
14326
14327     return result;
14328 }
14329
14330 /* [AS] Try to extract PV info from PGN comment */
14331 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14332 char *GetInfoFromComment( int index, char * text )
14333 {
14334     char * sep = text, *p;
14335
14336     if( text != NULL && index > 0 ) {
14337         int score = 0;
14338         int depth = 0;
14339         int time = -1, sec = 0, deci;
14340         char * s_eval = FindStr( text, "[%eval " );
14341         char * s_emt = FindStr( text, "[%emt " );
14342
14343         if( s_eval != NULL || s_emt != NULL ) {
14344             /* New style */
14345             char delim;
14346
14347             if( s_eval != NULL ) {
14348                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14349                     return text;
14350                 }
14351
14352                 if( delim != ']' ) {
14353                     return text;
14354                 }
14355             }
14356
14357             if( s_emt != NULL ) {
14358             }
14359                 return text;
14360         }
14361         else {
14362             /* We expect something like: [+|-]nnn.nn/dd */
14363             int score_lo = 0;
14364
14365             if(*text != '{') return text; // [HGM] braces: must be normal comment
14366
14367             sep = strchr( text, '/' );
14368             if( sep == NULL || sep < (text+4) ) {
14369                 return text;
14370             }
14371
14372             p = text;
14373             if(p[1] == '(') { // comment starts with PV
14374                p = strchr(p, ')'); // locate end of PV
14375                if(p == NULL || sep < p+5) return text;
14376                // at this point we have something like "{(.*) +0.23/6 ..."
14377                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14378                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14379                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14380             }
14381             time = -1; sec = -1; deci = -1;
14382             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14383                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14384                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14385                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14386                 return text;
14387             }
14388
14389             if( score_lo < 0 || score_lo >= 100 ) {
14390                 return text;
14391             }
14392
14393             if(sec >= 0) time = 600*time + 10*sec; else
14394             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14395
14396             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14397
14398             /* [HGM] PV time: now locate end of PV info */
14399             while( *++sep >= '0' && *sep <= '9'); // strip depth
14400             if(time >= 0)
14401             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14402             if(sec >= 0)
14403             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14404             if(deci >= 0)
14405             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14406             while(*sep == ' ') sep++;
14407         }
14408
14409         if( depth <= 0 ) {
14410             return text;
14411         }
14412
14413         if( time < 0 ) {
14414             time = -1;
14415         }
14416
14417         pvInfoList[index-1].depth = depth;
14418         pvInfoList[index-1].score = score;
14419         pvInfoList[index-1].time  = 10*time; // centi-sec
14420         if(*sep == '}') *sep = 0; else *--sep = '{';
14421         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14422     }
14423     return sep;
14424 }
14425
14426 void
14427 SendToProgram(message, cps)
14428      char *message;
14429      ChessProgramState *cps;
14430 {
14431     int count, outCount, error;
14432     char buf[MSG_SIZ];
14433
14434     if (cps->pr == NULL) return;
14435     Attention(cps);
14436
14437     if (appData.debugMode) {
14438         TimeMark now;
14439         GetTimeMark(&now);
14440         fprintf(debugFP, "%ld >%-6s: %s",
14441                 SubtractTimeMarks(&now, &programStartTime),
14442                 cps->which, message);
14443     }
14444
14445     count = strlen(message);
14446     outCount = OutputToProcess(cps->pr, message, count, &error);
14447     if (outCount < count && !exiting
14448                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14449       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14450       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14451         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14452             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14453                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14454                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14455                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14456             } else {
14457                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14458                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14459                 gameInfo.result = res;
14460             }
14461             gameInfo.resultDetails = StrSave(buf);
14462         }
14463         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14464         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14465     }
14466 }
14467
14468 void
14469 ReceiveFromProgram(isr, closure, message, count, error)
14470      InputSourceRef isr;
14471      VOIDSTAR closure;
14472      char *message;
14473      int count;
14474      int error;
14475 {
14476     char *end_str;
14477     char buf[MSG_SIZ];
14478     ChessProgramState *cps = (ChessProgramState *)closure;
14479
14480     if (isr != cps->isr) return; /* Killed intentionally */
14481     if (count <= 0) {
14482         if (count == 0) {
14483             RemoveInputSource(cps->isr);
14484             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14485             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14486                     _(cps->which), cps->program);
14487         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14488                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14489                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14490                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14491                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14492                 } else {
14493                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14494                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14495                     gameInfo.result = res;
14496                 }
14497                 gameInfo.resultDetails = StrSave(buf);
14498             }
14499             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14500             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14501         } else {
14502             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14503                     _(cps->which), cps->program);
14504             RemoveInputSource(cps->isr);
14505
14506             /* [AS] Program is misbehaving badly... kill it */
14507             if( count == -2 ) {
14508                 DestroyChildProcess( cps->pr, 9 );
14509                 cps->pr = NoProc;
14510             }
14511
14512             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14513         }
14514         return;
14515     }
14516
14517     if ((end_str = strchr(message, '\r')) != NULL)
14518       *end_str = NULLCHAR;
14519     if ((end_str = strchr(message, '\n')) != NULL)
14520       *end_str = NULLCHAR;
14521
14522     if (appData.debugMode) {
14523         TimeMark now; int print = 1;
14524         char *quote = ""; char c; int i;
14525
14526         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14527                 char start = message[0];
14528                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14529                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14530                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14531                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14532                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14533                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14534                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14535                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14536                    sscanf(message, "hint: %c", &c)!=1 && 
14537                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14538                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14539                     print = (appData.engineComments >= 2);
14540                 }
14541                 message[0] = start; // restore original message
14542         }
14543         if(print) {
14544                 GetTimeMark(&now);
14545                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14546                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14547                         quote,
14548                         message);
14549         }
14550     }
14551
14552     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14553     if (appData.icsEngineAnalyze) {
14554         if (strstr(message, "whisper") != NULL ||
14555              strstr(message, "kibitz") != NULL ||
14556             strstr(message, "tellics") != NULL) return;
14557     }
14558
14559     HandleMachineMove(message, cps);
14560 }
14561
14562
14563 void
14564 SendTimeControl(cps, mps, tc, inc, sd, st)
14565      ChessProgramState *cps;
14566      int mps, inc, sd, st;
14567      long tc;
14568 {
14569     char buf[MSG_SIZ];
14570     int seconds;
14571
14572     if( timeControl_2 > 0 ) {
14573         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14574             tc = timeControl_2;
14575         }
14576     }
14577     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14578     inc /= cps->timeOdds;
14579     st  /= cps->timeOdds;
14580
14581     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14582
14583     if (st > 0) {
14584       /* Set exact time per move, normally using st command */
14585       if (cps->stKludge) {
14586         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14587         seconds = st % 60;
14588         if (seconds == 0) {
14589           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14590         } else {
14591           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14592         }
14593       } else {
14594         snprintf(buf, MSG_SIZ, "st %d\n", st);
14595       }
14596     } else {
14597       /* Set conventional or incremental time control, using level command */
14598       if (seconds == 0) {
14599         /* Note old gnuchess bug -- minutes:seconds used to not work.
14600            Fixed in later versions, but still avoid :seconds
14601            when seconds is 0. */
14602         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14603       } else {
14604         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14605                  seconds, inc/1000.);
14606       }
14607     }
14608     SendToProgram(buf, cps);
14609
14610     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14611     /* Orthogonally, limit search to given depth */
14612     if (sd > 0) {
14613       if (cps->sdKludge) {
14614         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14615       } else {
14616         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14617       }
14618       SendToProgram(buf, cps);
14619     }
14620
14621     if(cps->nps >= 0) { /* [HGM] nps */
14622         if(cps->supportsNPS == FALSE)
14623           cps->nps = -1; // don't use if engine explicitly says not supported!
14624         else {
14625           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14626           SendToProgram(buf, cps);
14627         }
14628     }
14629 }
14630
14631 ChessProgramState *WhitePlayer()
14632 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14633 {
14634     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14635        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14636         return &second;
14637     return &first;
14638 }
14639
14640 void
14641 SendTimeRemaining(cps, machineWhite)
14642      ChessProgramState *cps;
14643      int /*boolean*/ machineWhite;
14644 {
14645     char message[MSG_SIZ];
14646     long time, otime;
14647
14648     /* Note: this routine must be called when the clocks are stopped
14649        or when they have *just* been set or switched; otherwise
14650        it will be off by the time since the current tick started.
14651     */
14652     if (machineWhite) {
14653         time = whiteTimeRemaining / 10;
14654         otime = blackTimeRemaining / 10;
14655     } else {
14656         time = blackTimeRemaining / 10;
14657         otime = whiteTimeRemaining / 10;
14658     }
14659     /* [HGM] translate opponent's time by time-odds factor */
14660     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14661     if (appData.debugMode) {
14662         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14663     }
14664
14665     if (time <= 0) time = 1;
14666     if (otime <= 0) otime = 1;
14667
14668     snprintf(message, MSG_SIZ, "time %ld\n", time);
14669     SendToProgram(message, cps);
14670
14671     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14672     SendToProgram(message, cps);
14673 }
14674
14675 int
14676 BoolFeature(p, name, loc, cps)
14677      char **p;
14678      char *name;
14679      int *loc;
14680      ChessProgramState *cps;
14681 {
14682   char buf[MSG_SIZ];
14683   int len = strlen(name);
14684   int val;
14685
14686   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14687     (*p) += len + 1;
14688     sscanf(*p, "%d", &val);
14689     *loc = (val != 0);
14690     while (**p && **p != ' ')
14691       (*p)++;
14692     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14693     SendToProgram(buf, cps);
14694     return TRUE;
14695   }
14696   return FALSE;
14697 }
14698
14699 int
14700 IntFeature(p, name, loc, cps)
14701      char **p;
14702      char *name;
14703      int *loc;
14704      ChessProgramState *cps;
14705 {
14706   char buf[MSG_SIZ];
14707   int len = strlen(name);
14708   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14709     (*p) += len + 1;
14710     sscanf(*p, "%d", loc);
14711     while (**p && **p != ' ') (*p)++;
14712     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14713     SendToProgram(buf, cps);
14714     return TRUE;
14715   }
14716   return FALSE;
14717 }
14718
14719 int
14720 StringFeature(p, name, loc, cps)
14721      char **p;
14722      char *name;
14723      char loc[];
14724      ChessProgramState *cps;
14725 {
14726   char buf[MSG_SIZ];
14727   int len = strlen(name);
14728   if (strncmp((*p), name, len) == 0
14729       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14730     (*p) += len + 2;
14731     sscanf(*p, "%[^\"]", loc);
14732     while (**p && **p != '\"') (*p)++;
14733     if (**p == '\"') (*p)++;
14734     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14735     SendToProgram(buf, cps);
14736     return TRUE;
14737   }
14738   return FALSE;
14739 }
14740
14741 int
14742 ParseOption(Option *opt, ChessProgramState *cps)
14743 // [HGM] options: process the string that defines an engine option, and determine
14744 // name, type, default value, and allowed value range
14745 {
14746         char *p, *q, buf[MSG_SIZ];
14747         int n, min = (-1)<<31, max = 1<<31, def;
14748
14749         if(p = strstr(opt->name, " -spin ")) {
14750             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14751             if(max < min) max = min; // enforce consistency
14752             if(def < min) def = min;
14753             if(def > max) def = max;
14754             opt->value = def;
14755             opt->min = min;
14756             opt->max = max;
14757             opt->type = Spin;
14758         } else if((p = strstr(opt->name, " -slider "))) {
14759             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14760             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14761             if(max < min) max = min; // enforce consistency
14762             if(def < min) def = min;
14763             if(def > max) def = max;
14764             opt->value = def;
14765             opt->min = min;
14766             opt->max = max;
14767             opt->type = Spin; // Slider;
14768         } else if((p = strstr(opt->name, " -string "))) {
14769             opt->textValue = p+9;
14770             opt->type = TextBox;
14771         } else if((p = strstr(opt->name, " -file "))) {
14772             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14773             opt->textValue = p+7;
14774             opt->type = FileName; // FileName;
14775         } else if((p = strstr(opt->name, " -path "))) {
14776             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14777             opt->textValue = p+7;
14778             opt->type = PathName; // PathName;
14779         } else if(p = strstr(opt->name, " -check ")) {
14780             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14781             opt->value = (def != 0);
14782             opt->type = CheckBox;
14783         } else if(p = strstr(opt->name, " -combo ")) {
14784             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14785             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14786             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14787             opt->value = n = 0;
14788             while(q = StrStr(q, " /// ")) {
14789                 n++; *q = 0;    // count choices, and null-terminate each of them
14790                 q += 5;
14791                 if(*q == '*') { // remember default, which is marked with * prefix
14792                     q++;
14793                     opt->value = n;
14794                 }
14795                 cps->comboList[cps->comboCnt++] = q;
14796             }
14797             cps->comboList[cps->comboCnt++] = NULL;
14798             opt->max = n + 1;
14799             opt->type = ComboBox;
14800         } else if(p = strstr(opt->name, " -button")) {
14801             opt->type = Button;
14802         } else if(p = strstr(opt->name, " -save")) {
14803             opt->type = SaveButton;
14804         } else return FALSE;
14805         *p = 0; // terminate option name
14806         // now look if the command-line options define a setting for this engine option.
14807         if(cps->optionSettings && cps->optionSettings[0])
14808             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14809         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14810           snprintf(buf, MSG_SIZ, "option %s", p);
14811                 if(p = strstr(buf, ",")) *p = 0;
14812                 if(q = strchr(buf, '=')) switch(opt->type) {
14813                     case ComboBox:
14814                         for(n=0; n<opt->max; n++)
14815                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14816                         break;
14817                     case TextBox:
14818                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14819                         break;
14820                     case Spin:
14821                     case CheckBox:
14822                         opt->value = atoi(q+1);
14823                     default:
14824                         break;
14825                 }
14826                 strcat(buf, "\n");
14827                 SendToProgram(buf, cps);
14828         }
14829         return TRUE;
14830 }
14831
14832 void
14833 FeatureDone(cps, val)
14834      ChessProgramState* cps;
14835      int val;
14836 {
14837   DelayedEventCallback cb = GetDelayedEvent();
14838   if ((cb == InitBackEnd3 && cps == &first) ||
14839       (cb == SettingsMenuIfReady && cps == &second) ||
14840       (cb == LoadEngine) ||
14841       (cb == TwoMachinesEventIfReady)) {
14842     CancelDelayedEvent();
14843     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14844   }
14845   cps->initDone = val;
14846 }
14847
14848 /* Parse feature command from engine */
14849 void
14850 ParseFeatures(args, cps)
14851      char* args;
14852      ChessProgramState *cps;
14853 {
14854   char *p = args;
14855   char *q;
14856   int val;
14857   char buf[MSG_SIZ];
14858
14859   for (;;) {
14860     while (*p == ' ') p++;
14861     if (*p == NULLCHAR) return;
14862
14863     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14864     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14865     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14866     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14867     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14868     if (BoolFeature(&p, "reuse", &val, cps)) {
14869       /* Engine can disable reuse, but can't enable it if user said no */
14870       if (!val) cps->reuse = FALSE;
14871       continue;
14872     }
14873     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14874     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14875       if (gameMode == TwoMachinesPlay) {
14876         DisplayTwoMachinesTitle();
14877       } else {
14878         DisplayTitle("");
14879       }
14880       continue;
14881     }
14882     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14883     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14884     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14885     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14886     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14887     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14888     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14889     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14890     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14891     if (IntFeature(&p, "done", &val, cps)) {
14892       FeatureDone(cps, val);
14893       continue;
14894     }
14895     /* Added by Tord: */
14896     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14897     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14898     /* End of additions by Tord */
14899
14900     /* [HGM] added features: */
14901     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14902     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14903     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14904     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14905     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14906     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14907     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14908         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14909           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14910             SendToProgram(buf, cps);
14911             continue;
14912         }
14913         if(cps->nrOptions >= MAX_OPTIONS) {
14914             cps->nrOptions--;
14915             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14916             DisplayError(buf, 0);
14917         }
14918         continue;
14919     }
14920     /* End of additions by HGM */
14921
14922     /* unknown feature: complain and skip */
14923     q = p;
14924     while (*q && *q != '=') q++;
14925     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14926     SendToProgram(buf, cps);
14927     p = q;
14928     if (*p == '=') {
14929       p++;
14930       if (*p == '\"') {
14931         p++;
14932         while (*p && *p != '\"') p++;
14933         if (*p == '\"') p++;
14934       } else {
14935         while (*p && *p != ' ') p++;
14936       }
14937     }
14938   }
14939
14940 }
14941
14942 void
14943 PeriodicUpdatesEvent(newState)
14944      int newState;
14945 {
14946     if (newState == appData.periodicUpdates)
14947       return;
14948
14949     appData.periodicUpdates=newState;
14950
14951     /* Display type changes, so update it now */
14952 //    DisplayAnalysis();
14953
14954     /* Get the ball rolling again... */
14955     if (newState) {
14956         AnalysisPeriodicEvent(1);
14957         StartAnalysisClock();
14958     }
14959 }
14960
14961 void
14962 PonderNextMoveEvent(newState)
14963      int newState;
14964 {
14965     if (newState == appData.ponderNextMove) return;
14966     if (gameMode == EditPosition) EditPositionDone(TRUE);
14967     if (newState) {
14968         SendToProgram("hard\n", &first);
14969         if (gameMode == TwoMachinesPlay) {
14970             SendToProgram("hard\n", &second);
14971         }
14972     } else {
14973         SendToProgram("easy\n", &first);
14974         thinkOutput[0] = NULLCHAR;
14975         if (gameMode == TwoMachinesPlay) {
14976             SendToProgram("easy\n", &second);
14977         }
14978     }
14979     appData.ponderNextMove = newState;
14980 }
14981
14982 void
14983 NewSettingEvent(option, feature, command, value)
14984      char *command;
14985      int option, value, *feature;
14986 {
14987     char buf[MSG_SIZ];
14988
14989     if (gameMode == EditPosition) EditPositionDone(TRUE);
14990     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14991     if(feature == NULL || *feature) SendToProgram(buf, &first);
14992     if (gameMode == TwoMachinesPlay) {
14993         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14994     }
14995 }
14996
14997 void
14998 ShowThinkingEvent()
14999 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15000 {
15001     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15002     int newState = appData.showThinking
15003         // [HGM] thinking: other features now need thinking output as well
15004         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15005
15006     if (oldState == newState) return;
15007     oldState = newState;
15008     if (gameMode == EditPosition) EditPositionDone(TRUE);
15009     if (oldState) {
15010         SendToProgram("post\n", &first);
15011         if (gameMode == TwoMachinesPlay) {
15012             SendToProgram("post\n", &second);
15013         }
15014     } else {
15015         SendToProgram("nopost\n", &first);
15016         thinkOutput[0] = NULLCHAR;
15017         if (gameMode == TwoMachinesPlay) {
15018             SendToProgram("nopost\n", &second);
15019         }
15020     }
15021 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15022 }
15023
15024 void
15025 AskQuestionEvent(title, question, replyPrefix, which)
15026      char *title; char *question; char *replyPrefix; char *which;
15027 {
15028   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15029   if (pr == NoProc) return;
15030   AskQuestion(title, question, replyPrefix, pr);
15031 }
15032
15033 void
15034 TypeInEvent(char firstChar)
15035 {
15036     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15037         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15038         gameMode == AnalyzeMode || gameMode == EditGame || \r
15039         gameMode == EditPosition || gameMode == IcsExamining ||\r
15040         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15041         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15042                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15043                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15044         gameMode == Training) PopUpMoveDialog(firstChar);
15045 }
15046
15047 void
15048 TypeInDoneEvent(char *move)
15049 {
15050         Board board;
15051         int n, fromX, fromY, toX, toY;
15052         char promoChar;
15053         ChessMove moveType;\r
15054
15055         // [HGM] FENedit\r
15056         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15057                 EditPositionPasteFEN(move);\r
15058                 return;\r
15059         }\r
15060         // [HGM] movenum: allow move number to be typed in any mode\r
15061         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15062           ToNrEvent(2*n-1);\r
15063           return;\r
15064         }\r
15065
15066       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15067         gameMode != Training) {\r
15068         DisplayMoveError(_("Displayed move is not current"));\r
15069       } else {\r
15070         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15071           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15072         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15073         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15074           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15075           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15076         } else {\r
15077           DisplayMoveError(_("Could not parse move"));\r
15078         }
15079       }\r
15080 }\r
15081
15082 void
15083 DisplayMove(moveNumber)
15084      int moveNumber;
15085 {
15086     char message[MSG_SIZ];
15087     char res[MSG_SIZ];
15088     char cpThinkOutput[MSG_SIZ];
15089
15090     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15091
15092     if (moveNumber == forwardMostMove - 1 ||
15093         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15094
15095         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15096
15097         if (strchr(cpThinkOutput, '\n')) {
15098             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15099         }
15100     } else {
15101         *cpThinkOutput = NULLCHAR;
15102     }
15103
15104     /* [AS] Hide thinking from human user */
15105     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15106         *cpThinkOutput = NULLCHAR;
15107         if( thinkOutput[0] != NULLCHAR ) {
15108             int i;
15109
15110             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15111                 cpThinkOutput[i] = '.';
15112             }
15113             cpThinkOutput[i] = NULLCHAR;
15114             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15115         }
15116     }
15117
15118     if (moveNumber == forwardMostMove - 1 &&
15119         gameInfo.resultDetails != NULL) {
15120         if (gameInfo.resultDetails[0] == NULLCHAR) {
15121           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15122         } else {
15123           snprintf(res, MSG_SIZ, " {%s} %s",
15124                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15125         }
15126     } else {
15127         res[0] = NULLCHAR;
15128     }
15129
15130     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15131         DisplayMessage(res, cpThinkOutput);
15132     } else {
15133       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15134                 WhiteOnMove(moveNumber) ? " " : ".. ",
15135                 parseList[moveNumber], res);
15136         DisplayMessage(message, cpThinkOutput);
15137     }
15138 }
15139
15140 void
15141 DisplayComment(moveNumber, text)
15142      int moveNumber;
15143      char *text;
15144 {
15145     char title[MSG_SIZ];
15146     char buf[8000]; // comment can be long!
15147     int score, depth;
15148
15149     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15150       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15151     } else {
15152       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15153               WhiteOnMove(moveNumber) ? " " : ".. ",
15154               parseList[moveNumber]);
15155     }
15156     // [HGM] PV info: display PV info together with (or as) comment
15157     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15158       if(text == NULL) text = "";
15159       score = pvInfoList[moveNumber].score;
15160       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15161               depth, (pvInfoList[moveNumber].time+50)/100, text);
15162       text = buf;
15163     }
15164     if (text != NULL && (appData.autoDisplayComment || commentUp))
15165         CommentPopUp(title, text);
15166 }
15167
15168 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15169  * might be busy thinking or pondering.  It can be omitted if your
15170  * gnuchess is configured to stop thinking immediately on any user
15171  * input.  However, that gnuchess feature depends on the FIONREAD
15172  * ioctl, which does not work properly on some flavors of Unix.
15173  */
15174 void
15175 Attention(cps)
15176      ChessProgramState *cps;
15177 {
15178 #if ATTENTION
15179     if (!cps->useSigint) return;
15180     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15181     switch (gameMode) {
15182       case MachinePlaysWhite:
15183       case MachinePlaysBlack:
15184       case TwoMachinesPlay:
15185       case IcsPlayingWhite:
15186       case IcsPlayingBlack:
15187       case AnalyzeMode:
15188       case AnalyzeFile:
15189         /* Skip if we know it isn't thinking */
15190         if (!cps->maybeThinking) return;
15191         if (appData.debugMode)
15192           fprintf(debugFP, "Interrupting %s\n", cps->which);
15193         InterruptChildProcess(cps->pr);
15194         cps->maybeThinking = FALSE;
15195         break;
15196       default:
15197         break;
15198     }
15199 #endif /*ATTENTION*/
15200 }
15201
15202 int
15203 CheckFlags()
15204 {
15205     if (whiteTimeRemaining <= 0) {
15206         if (!whiteFlag) {
15207             whiteFlag = TRUE;
15208             if (appData.icsActive) {
15209                 if (appData.autoCallFlag &&
15210                     gameMode == IcsPlayingBlack && !blackFlag) {
15211                   SendToICS(ics_prefix);
15212                   SendToICS("flag\n");
15213                 }
15214             } else {
15215                 if (blackFlag) {
15216                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15217                 } else {
15218                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15219                     if (appData.autoCallFlag) {
15220                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15221                         return TRUE;
15222                     }
15223                 }
15224             }
15225         }
15226     }
15227     if (blackTimeRemaining <= 0) {
15228         if (!blackFlag) {
15229             blackFlag = TRUE;
15230             if (appData.icsActive) {
15231                 if (appData.autoCallFlag &&
15232                     gameMode == IcsPlayingWhite && !whiteFlag) {
15233                   SendToICS(ics_prefix);
15234                   SendToICS("flag\n");
15235                 }
15236             } else {
15237                 if (whiteFlag) {
15238                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15239                 } else {
15240                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15241                     if (appData.autoCallFlag) {
15242                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15243                         return TRUE;
15244                     }
15245                 }
15246             }
15247         }
15248     }
15249     return FALSE;
15250 }
15251
15252 void
15253 CheckTimeControl()
15254 {
15255     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15256         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15257
15258     /*
15259      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15260      */
15261     if ( !WhiteOnMove(forwardMostMove) ) {
15262         /* White made time control */
15263         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15264         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15265         /* [HGM] time odds: correct new time quota for time odds! */
15266                                             / WhitePlayer()->timeOdds;
15267         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15268     } else {
15269         lastBlack -= blackTimeRemaining;
15270         /* Black made time control */
15271         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15272                                             / WhitePlayer()->other->timeOdds;
15273         lastWhite = whiteTimeRemaining;
15274     }
15275 }
15276
15277 void
15278 DisplayBothClocks()
15279 {
15280     int wom = gameMode == EditPosition ?
15281       !blackPlaysFirst : WhiteOnMove(currentMove);
15282     DisplayWhiteClock(whiteTimeRemaining, wom);
15283     DisplayBlackClock(blackTimeRemaining, !wom);
15284 }
15285
15286
15287 /* Timekeeping seems to be a portability nightmare.  I think everyone
15288    has ftime(), but I'm really not sure, so I'm including some ifdefs
15289    to use other calls if you don't.  Clocks will be less accurate if
15290    you have neither ftime nor gettimeofday.
15291 */
15292
15293 /* VS 2008 requires the #include outside of the function */
15294 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15295 #include <sys/timeb.h>
15296 #endif
15297
15298 /* Get the current time as a TimeMark */
15299 void
15300 GetTimeMark(tm)
15301      TimeMark *tm;
15302 {
15303 #if HAVE_GETTIMEOFDAY
15304
15305     struct timeval timeVal;
15306     struct timezone timeZone;
15307
15308     gettimeofday(&timeVal, &timeZone);
15309     tm->sec = (long) timeVal.tv_sec;
15310     tm->ms = (int) (timeVal.tv_usec / 1000L);
15311
15312 #else /*!HAVE_GETTIMEOFDAY*/
15313 #if HAVE_FTIME
15314
15315 // include <sys/timeb.h> / moved to just above start of function
15316     struct timeb timeB;
15317
15318     ftime(&timeB);
15319     tm->sec = (long) timeB.time;
15320     tm->ms = (int) timeB.millitm;
15321
15322 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15323     tm->sec = (long) time(NULL);
15324     tm->ms = 0;
15325 #endif
15326 #endif
15327 }
15328
15329 /* Return the difference in milliseconds between two
15330    time marks.  We assume the difference will fit in a long!
15331 */
15332 long
15333 SubtractTimeMarks(tm2, tm1)
15334      TimeMark *tm2, *tm1;
15335 {
15336     return 1000L*(tm2->sec - tm1->sec) +
15337            (long) (tm2->ms - tm1->ms);
15338 }
15339
15340
15341 /*
15342  * Code to manage the game clocks.
15343  *
15344  * In tournament play, black starts the clock and then white makes a move.
15345  * We give the human user a slight advantage if he is playing white---the
15346  * clocks don't run until he makes his first move, so it takes zero time.
15347  * Also, we don't account for network lag, so we could get out of sync
15348  * with GNU Chess's clock -- but then, referees are always right.
15349  */
15350
15351 static TimeMark tickStartTM;
15352 static long intendedTickLength;
15353
15354 long
15355 NextTickLength(timeRemaining)
15356      long timeRemaining;
15357 {
15358     long nominalTickLength, nextTickLength;
15359
15360     if (timeRemaining > 0L && timeRemaining <= 10000L)
15361       nominalTickLength = 100L;
15362     else
15363       nominalTickLength = 1000L;
15364     nextTickLength = timeRemaining % nominalTickLength;
15365     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15366
15367     return nextTickLength;
15368 }
15369
15370 /* Adjust clock one minute up or down */
15371 void
15372 AdjustClock(Boolean which, int dir)
15373 {
15374     if(which) blackTimeRemaining += 60000*dir;
15375     else      whiteTimeRemaining += 60000*dir;
15376     DisplayBothClocks();
15377 }
15378
15379 /* Stop clocks and reset to a fresh time control */
15380 void
15381 ResetClocks()
15382 {
15383     (void) StopClockTimer();
15384     if (appData.icsActive) {
15385         whiteTimeRemaining = blackTimeRemaining = 0;
15386     } else if (searchTime) {
15387         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15388         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15389     } else { /* [HGM] correct new time quote for time odds */
15390         whiteTC = blackTC = fullTimeControlString;
15391         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15392         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15393     }
15394     if (whiteFlag || blackFlag) {
15395         DisplayTitle("");
15396         whiteFlag = blackFlag = FALSE;
15397     }
15398     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15399     DisplayBothClocks();
15400 }
15401
15402 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15403
15404 /* Decrement running clock by amount of time that has passed */
15405 void
15406 DecrementClocks()
15407 {
15408     long timeRemaining;
15409     long lastTickLength, fudge;
15410     TimeMark now;
15411
15412     if (!appData.clockMode) return;
15413     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15414
15415     GetTimeMark(&now);
15416
15417     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15418
15419     /* Fudge if we woke up a little too soon */
15420     fudge = intendedTickLength - lastTickLength;
15421     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15422
15423     if (WhiteOnMove(forwardMostMove)) {
15424         if(whiteNPS >= 0) lastTickLength = 0;
15425         timeRemaining = whiteTimeRemaining -= lastTickLength;
15426         if(timeRemaining < 0 && !appData.icsActive) {
15427             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15428             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15429                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15430                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15431             }
15432         }
15433         DisplayWhiteClock(whiteTimeRemaining - fudge,
15434                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15435     } else {
15436         if(blackNPS >= 0) lastTickLength = 0;
15437         timeRemaining = blackTimeRemaining -= lastTickLength;
15438         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15439             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15440             if(suddenDeath) {
15441                 blackStartMove = forwardMostMove;
15442                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15443             }
15444         }
15445         DisplayBlackClock(blackTimeRemaining - fudge,
15446                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15447     }
15448     if (CheckFlags()) return;
15449
15450     tickStartTM = now;
15451     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15452     StartClockTimer(intendedTickLength);
15453
15454     /* if the time remaining has fallen below the alarm threshold, sound the
15455      * alarm. if the alarm has sounded and (due to a takeback or time control
15456      * with increment) the time remaining has increased to a level above the
15457      * threshold, reset the alarm so it can sound again.
15458      */
15459
15460     if (appData.icsActive && appData.icsAlarm) {
15461
15462         /* make sure we are dealing with the user's clock */
15463         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15464                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15465            )) return;
15466
15467         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15468             alarmSounded = FALSE;
15469         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15470             PlayAlarmSound();
15471             alarmSounded = TRUE;
15472         }
15473     }
15474 }
15475
15476
15477 /* A player has just moved, so stop the previously running
15478    clock and (if in clock mode) start the other one.
15479    We redisplay both clocks in case we're in ICS mode, because
15480    ICS gives us an update to both clocks after every move.
15481    Note that this routine is called *after* forwardMostMove
15482    is updated, so the last fractional tick must be subtracted
15483    from the color that is *not* on move now.
15484 */
15485 void
15486 SwitchClocks(int newMoveNr)
15487 {
15488     long lastTickLength;
15489     TimeMark now;
15490     int flagged = FALSE;
15491
15492     GetTimeMark(&now);
15493
15494     if (StopClockTimer() && appData.clockMode) {
15495         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15496         if (!WhiteOnMove(forwardMostMove)) {
15497             if(blackNPS >= 0) lastTickLength = 0;
15498             blackTimeRemaining -= lastTickLength;
15499            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15500 //         if(pvInfoList[forwardMostMove].time == -1)
15501                  pvInfoList[forwardMostMove].time =               // use GUI time
15502                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15503         } else {
15504            if(whiteNPS >= 0) lastTickLength = 0;
15505            whiteTimeRemaining -= lastTickLength;
15506            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15507 //         if(pvInfoList[forwardMostMove].time == -1)
15508                  pvInfoList[forwardMostMove].time =
15509                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15510         }
15511         flagged = CheckFlags();
15512     }
15513     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15514     CheckTimeControl();
15515
15516     if (flagged || !appData.clockMode) return;
15517
15518     switch (gameMode) {
15519       case MachinePlaysBlack:
15520       case MachinePlaysWhite:
15521       case BeginningOfGame:
15522         if (pausing) return;
15523         break;
15524
15525       case EditGame:
15526       case PlayFromGameFile:
15527       case IcsExamining:
15528         return;
15529
15530       default:
15531         break;
15532     }
15533
15534     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15535         if(WhiteOnMove(forwardMostMove))
15536              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15537         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15538     }
15539
15540     tickStartTM = now;
15541     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15542       whiteTimeRemaining : blackTimeRemaining);
15543     StartClockTimer(intendedTickLength);
15544 }
15545
15546
15547 /* Stop both clocks */
15548 void
15549 StopClocks()
15550 {
15551     long lastTickLength;
15552     TimeMark now;
15553
15554     if (!StopClockTimer()) return;
15555     if (!appData.clockMode) return;
15556
15557     GetTimeMark(&now);
15558
15559     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15560     if (WhiteOnMove(forwardMostMove)) {
15561         if(whiteNPS >= 0) lastTickLength = 0;
15562         whiteTimeRemaining -= lastTickLength;
15563         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15564     } else {
15565         if(blackNPS >= 0) lastTickLength = 0;
15566         blackTimeRemaining -= lastTickLength;
15567         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15568     }
15569     CheckFlags();
15570 }
15571
15572 /* Start clock of player on move.  Time may have been reset, so
15573    if clock is already running, stop and restart it. */
15574 void
15575 StartClocks()
15576 {
15577     (void) StopClockTimer(); /* in case it was running already */
15578     DisplayBothClocks();
15579     if (CheckFlags()) return;
15580
15581     if (!appData.clockMode) return;
15582     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15583
15584     GetTimeMark(&tickStartTM);
15585     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15586       whiteTimeRemaining : blackTimeRemaining);
15587
15588    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15589     whiteNPS = blackNPS = -1;
15590     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15591        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15592         whiteNPS = first.nps;
15593     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15594        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15595         blackNPS = first.nps;
15596     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15597         whiteNPS = second.nps;
15598     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15599         blackNPS = second.nps;
15600     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15601
15602     StartClockTimer(intendedTickLength);
15603 }
15604
15605 char *
15606 TimeString(ms)
15607      long ms;
15608 {
15609     long second, minute, hour, day;
15610     char *sign = "";
15611     static char buf[32];
15612
15613     if (ms > 0 && ms <= 9900) {
15614       /* convert milliseconds to tenths, rounding up */
15615       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15616
15617       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15618       return buf;
15619     }
15620
15621     /* convert milliseconds to seconds, rounding up */
15622     /* use floating point to avoid strangeness of integer division
15623        with negative dividends on many machines */
15624     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15625
15626     if (second < 0) {
15627         sign = "-";
15628         second = -second;
15629     }
15630
15631     day = second / (60 * 60 * 24);
15632     second = second % (60 * 60 * 24);
15633     hour = second / (60 * 60);
15634     second = second % (60 * 60);
15635     minute = second / 60;
15636     second = second % 60;
15637
15638     if (day > 0)
15639       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15640               sign, day, hour, minute, second);
15641     else if (hour > 0)
15642       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15643     else
15644       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15645
15646     return buf;
15647 }
15648
15649
15650 /*
15651  * This is necessary because some C libraries aren't ANSI C compliant yet.
15652  */
15653 char *
15654 StrStr(string, match)
15655      char *string, *match;
15656 {
15657     int i, length;
15658
15659     length = strlen(match);
15660
15661     for (i = strlen(string) - length; i >= 0; i--, string++)
15662       if (!strncmp(match, string, length))
15663         return string;
15664
15665     return NULL;
15666 }
15667
15668 char *
15669 StrCaseStr(string, match)
15670      char *string, *match;
15671 {
15672     int i, j, length;
15673
15674     length = strlen(match);
15675
15676     for (i = strlen(string) - length; i >= 0; i--, string++) {
15677         for (j = 0; j < length; j++) {
15678             if (ToLower(match[j]) != ToLower(string[j]))
15679               break;
15680         }
15681         if (j == length) return string;
15682     }
15683
15684     return NULL;
15685 }
15686
15687 #ifndef _amigados
15688 int
15689 StrCaseCmp(s1, s2)
15690      char *s1, *s2;
15691 {
15692     char c1, c2;
15693
15694     for (;;) {
15695         c1 = ToLower(*s1++);
15696         c2 = ToLower(*s2++);
15697         if (c1 > c2) return 1;
15698         if (c1 < c2) return -1;
15699         if (c1 == NULLCHAR) return 0;
15700     }
15701 }
15702
15703
15704 int
15705 ToLower(c)
15706      int c;
15707 {
15708     return isupper(c) ? tolower(c) : c;
15709 }
15710
15711
15712 int
15713 ToUpper(c)
15714      int c;
15715 {
15716     return islower(c) ? toupper(c) : c;
15717 }
15718 #endif /* !_amigados    */
15719
15720 char *
15721 StrSave(s)
15722      char *s;
15723 {
15724   char *ret;
15725
15726   if ((ret = (char *) malloc(strlen(s) + 1)))
15727     {
15728       safeStrCpy(ret, s, strlen(s)+1);
15729     }
15730   return ret;
15731 }
15732
15733 char *
15734 StrSavePtr(s, savePtr)
15735      char *s, **savePtr;
15736 {
15737     if (*savePtr) {
15738         free(*savePtr);
15739     }
15740     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15741       safeStrCpy(*savePtr, s, strlen(s)+1);
15742     }
15743     return(*savePtr);
15744 }
15745
15746 char *
15747 PGNDate()
15748 {
15749     time_t clock;
15750     struct tm *tm;
15751     char buf[MSG_SIZ];
15752
15753     clock = time((time_t *)NULL);
15754     tm = localtime(&clock);
15755     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15756             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15757     return StrSave(buf);
15758 }
15759
15760
15761 char *
15762 PositionToFEN(move, overrideCastling)
15763      int move;
15764      char *overrideCastling;
15765 {
15766     int i, j, fromX, fromY, toX, toY;
15767     int whiteToPlay;
15768     char buf[128];
15769     char *p, *q;
15770     int emptycount;
15771     ChessSquare piece;
15772
15773     whiteToPlay = (gameMode == EditPosition) ?
15774       !blackPlaysFirst : (move % 2 == 0);
15775     p = buf;
15776
15777     /* Piece placement data */
15778     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15779         emptycount = 0;
15780         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15781             if (boards[move][i][j] == EmptySquare) {
15782                 emptycount++;
15783             } else { ChessSquare piece = boards[move][i][j];
15784                 if (emptycount > 0) {
15785                     if(emptycount<10) /* [HGM] can be >= 10 */
15786                         *p++ = '0' + emptycount;
15787                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15788                     emptycount = 0;
15789                 }
15790                 if(PieceToChar(piece) == '+') {
15791                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15792                     *p++ = '+';
15793                     piece = (ChessSquare)(DEMOTED piece);
15794                 }
15795                 *p++ = PieceToChar(piece);
15796                 if(p[-1] == '~') {
15797                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15798                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15799                     *p++ = '~';
15800                 }
15801             }
15802         }
15803         if (emptycount > 0) {
15804             if(emptycount<10) /* [HGM] can be >= 10 */
15805                 *p++ = '0' + emptycount;
15806             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15807             emptycount = 0;
15808         }
15809         *p++ = '/';
15810     }
15811     *(p - 1) = ' ';
15812
15813     /* [HGM] print Crazyhouse or Shogi holdings */
15814     if( gameInfo.holdingsWidth ) {
15815         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15816         q = p;
15817         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15818             piece = boards[move][i][BOARD_WIDTH-1];
15819             if( piece != EmptySquare )
15820               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15821                   *p++ = PieceToChar(piece);
15822         }
15823         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15824             piece = boards[move][BOARD_HEIGHT-i-1][0];
15825             if( piece != EmptySquare )
15826               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15827                   *p++ = PieceToChar(piece);
15828         }
15829
15830         if( q == p ) *p++ = '-';
15831         *p++ = ']';
15832         *p++ = ' ';
15833     }
15834
15835     /* Active color */
15836     *p++ = whiteToPlay ? 'w' : 'b';
15837     *p++ = ' ';
15838
15839   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15840     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15841   } else {
15842   if(nrCastlingRights) {
15843      q = p;
15844      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15845        /* [HGM] write directly from rights */
15846            if(boards[move][CASTLING][2] != NoRights &&
15847               boards[move][CASTLING][0] != NoRights   )
15848                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15849            if(boards[move][CASTLING][2] != NoRights &&
15850               boards[move][CASTLING][1] != NoRights   )
15851                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15852            if(boards[move][CASTLING][5] != NoRights &&
15853               boards[move][CASTLING][3] != NoRights   )
15854                 *p++ = boards[move][CASTLING][3] + AAA;
15855            if(boards[move][CASTLING][5] != NoRights &&
15856               boards[move][CASTLING][4] != NoRights   )
15857                 *p++ = boards[move][CASTLING][4] + AAA;
15858      } else {
15859
15860         /* [HGM] write true castling rights */
15861         if( nrCastlingRights == 6 ) {
15862             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15863                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15864             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15865                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15866             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15867                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15868             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15869                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15870         }
15871      }
15872      if (q == p) *p++ = '-'; /* No castling rights */
15873      *p++ = ' ';
15874   }
15875
15876   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15877      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15878     /* En passant target square */
15879     if (move > backwardMostMove) {
15880         fromX = moveList[move - 1][0] - AAA;
15881         fromY = moveList[move - 1][1] - ONE;
15882         toX = moveList[move - 1][2] - AAA;
15883         toY = moveList[move - 1][3] - ONE;
15884         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15885             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15886             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15887             fromX == toX) {
15888             /* 2-square pawn move just happened */
15889             *p++ = toX + AAA;
15890             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15891         } else {
15892             *p++ = '-';
15893         }
15894     } else if(move == backwardMostMove) {
15895         // [HGM] perhaps we should always do it like this, and forget the above?
15896         if((signed char)boards[move][EP_STATUS] >= 0) {
15897             *p++ = boards[move][EP_STATUS] + AAA;
15898             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15899         } else {
15900             *p++ = '-';
15901         }
15902     } else {
15903         *p++ = '-';
15904     }
15905     *p++ = ' ';
15906   }
15907   }
15908
15909     /* [HGM] find reversible plies */
15910     {   int i = 0, j=move;
15911
15912         if (appData.debugMode) { int k;
15913             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15914             for(k=backwardMostMove; k<=forwardMostMove; k++)
15915                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15916
15917         }
15918
15919         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15920         if( j == backwardMostMove ) i += initialRulePlies;
15921         sprintf(p, "%d ", i);
15922         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15923     }
15924     /* Fullmove number */
15925     sprintf(p, "%d", (move / 2) + 1);
15926
15927     return StrSave(buf);
15928 }
15929
15930 Boolean
15931 ParseFEN(board, blackPlaysFirst, fen)
15932     Board board;
15933      int *blackPlaysFirst;
15934      char *fen;
15935 {
15936     int i, j;
15937     char *p, c;
15938     int emptycount;
15939     ChessSquare piece;
15940
15941     p = fen;
15942
15943     /* [HGM] by default clear Crazyhouse holdings, if present */
15944     if(gameInfo.holdingsWidth) {
15945        for(i=0; i<BOARD_HEIGHT; i++) {
15946            board[i][0]             = EmptySquare; /* black holdings */
15947            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15948            board[i][1]             = (ChessSquare) 0; /* black counts */
15949            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15950        }
15951     }
15952
15953     /* Piece placement data */
15954     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15955         j = 0;
15956         for (;;) {
15957             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15958                 if (*p == '/') p++;
15959                 emptycount = gameInfo.boardWidth - j;
15960                 while (emptycount--)
15961                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15962                 break;
15963 #if(BOARD_FILES >= 10)
15964             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15965                 p++; emptycount=10;
15966                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15967                 while (emptycount--)
15968                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15969 #endif
15970             } else if (isdigit(*p)) {
15971                 emptycount = *p++ - '0';
15972                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15973                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15974                 while (emptycount--)
15975                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15976             } else if (*p == '+' || isalpha(*p)) {
15977                 if (j >= gameInfo.boardWidth) return FALSE;
15978                 if(*p=='+') {
15979                     piece = CharToPiece(*++p);
15980                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15981                     piece = (ChessSquare) (PROMOTED piece ); p++;
15982                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15983                 } else piece = CharToPiece(*p++);
15984
15985                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15986                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15987                     piece = (ChessSquare) (PROMOTED piece);
15988                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15989                     p++;
15990                 }
15991                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15992             } else {
15993                 return FALSE;
15994             }
15995         }
15996     }
15997     while (*p == '/' || *p == ' ') p++;
15998
15999     /* [HGM] look for Crazyhouse holdings here */
16000     while(*p==' ') p++;
16001     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16002         if(*p == '[') p++;
16003         if(*p == '-' ) p++; /* empty holdings */ else {
16004             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16005             /* if we would allow FEN reading to set board size, we would   */
16006             /* have to add holdings and shift the board read so far here   */
16007             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16008                 p++;
16009                 if((int) piece >= (int) BlackPawn ) {
16010                     i = (int)piece - (int)BlackPawn;
16011                     i = PieceToNumber((ChessSquare)i);
16012                     if( i >= gameInfo.holdingsSize ) return FALSE;
16013                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16014                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16015                 } else {
16016                     i = (int)piece - (int)WhitePawn;
16017                     i = PieceToNumber((ChessSquare)i);
16018                     if( i >= gameInfo.holdingsSize ) return FALSE;
16019                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16020                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16021                 }
16022             }
16023         }
16024         if(*p == ']') p++;
16025     }
16026
16027     while(*p == ' ') p++;
16028
16029     /* Active color */
16030     c = *p++;
16031     if(appData.colorNickNames) {
16032       if( c == appData.colorNickNames[0] ) c = 'w'; else
16033       if( c == appData.colorNickNames[1] ) c = 'b';
16034     }
16035     switch (c) {
16036       case 'w':
16037         *blackPlaysFirst = FALSE;
16038         break;
16039       case 'b':
16040         *blackPlaysFirst = TRUE;
16041         break;
16042       default:
16043         return FALSE;
16044     }
16045
16046     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16047     /* return the extra info in global variiables             */
16048
16049     /* set defaults in case FEN is incomplete */
16050     board[EP_STATUS] = EP_UNKNOWN;
16051     for(i=0; i<nrCastlingRights; i++ ) {
16052         board[CASTLING][i] =
16053             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16054     }   /* assume possible unless obviously impossible */
16055     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16056     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16057     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16058                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16059     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16060     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16061     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16062                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16063     FENrulePlies = 0;
16064
16065     while(*p==' ') p++;
16066     if(nrCastlingRights) {
16067       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16068           /* castling indicator present, so default becomes no castlings */
16069           for(i=0; i<nrCastlingRights; i++ ) {
16070                  board[CASTLING][i] = NoRights;
16071           }
16072       }
16073       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16074              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16075              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16076              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16077         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16078
16079         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16080             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16081             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16082         }
16083         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16084             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16085         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16086                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16087         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16088                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16089         switch(c) {
16090           case'K':
16091               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16092               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16093               board[CASTLING][2] = whiteKingFile;
16094               break;
16095           case'Q':
16096               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16097               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16098               board[CASTLING][2] = whiteKingFile;
16099               break;
16100           case'k':
16101               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16102               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16103               board[CASTLING][5] = blackKingFile;
16104               break;
16105           case'q':
16106               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16107               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16108               board[CASTLING][5] = blackKingFile;
16109           case '-':
16110               break;
16111           default: /* FRC castlings */
16112               if(c >= 'a') { /* black rights */
16113                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16114                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16115                   if(i == BOARD_RGHT) break;
16116                   board[CASTLING][5] = i;
16117                   c -= AAA;
16118                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16119                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16120                   if(c > i)
16121                       board[CASTLING][3] = c;
16122                   else
16123                       board[CASTLING][4] = c;
16124               } else { /* white rights */
16125                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16126                     if(board[0][i] == WhiteKing) break;
16127                   if(i == BOARD_RGHT) break;
16128                   board[CASTLING][2] = i;
16129                   c -= AAA - 'a' + 'A';
16130                   if(board[0][c] >= WhiteKing) break;
16131                   if(c > i)
16132                       board[CASTLING][0] = c;
16133                   else
16134                       board[CASTLING][1] = c;
16135               }
16136         }
16137       }
16138       for(i=0; i<nrCastlingRights; i++)
16139         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16140     if (appData.debugMode) {
16141         fprintf(debugFP, "FEN castling rights:");
16142         for(i=0; i<nrCastlingRights; i++)
16143         fprintf(debugFP, " %d", board[CASTLING][i]);
16144         fprintf(debugFP, "\n");
16145     }
16146
16147       while(*p==' ') p++;
16148     }
16149
16150     /* read e.p. field in games that know e.p. capture */
16151     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16152        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16153       if(*p=='-') {
16154         p++; board[EP_STATUS] = EP_NONE;
16155       } else {
16156          char c = *p++ - AAA;
16157
16158          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16159          if(*p >= '0' && *p <='9') p++;
16160          board[EP_STATUS] = c;
16161       }
16162     }
16163
16164
16165     if(sscanf(p, "%d", &i) == 1) {
16166         FENrulePlies = i; /* 50-move ply counter */
16167         /* (The move number is still ignored)    */
16168     }
16169
16170     return TRUE;
16171 }
16172
16173 void
16174 EditPositionPasteFEN(char *fen)
16175 {
16176   if (fen != NULL) {
16177     Board initial_position;
16178
16179     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16180       DisplayError(_("Bad FEN position in clipboard"), 0);
16181       return ;
16182     } else {
16183       int savedBlackPlaysFirst = blackPlaysFirst;
16184       EditPositionEvent();
16185       blackPlaysFirst = savedBlackPlaysFirst;
16186       CopyBoard(boards[0], initial_position);
16187       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16188       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16189       DisplayBothClocks();
16190       DrawPosition(FALSE, boards[currentMove]);
16191     }
16192   }
16193 }
16194
16195 static char cseq[12] = "\\   ";
16196
16197 Boolean set_cont_sequence(char *new_seq)
16198 {
16199     int len;
16200     Boolean ret;
16201
16202     // handle bad attempts to set the sequence
16203         if (!new_seq)
16204                 return 0; // acceptable error - no debug
16205
16206     len = strlen(new_seq);
16207     ret = (len > 0) && (len < sizeof(cseq));
16208     if (ret)
16209       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16210     else if (appData.debugMode)
16211       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16212     return ret;
16213 }
16214
16215 /*
16216     reformat a source message so words don't cross the width boundary.  internal
16217     newlines are not removed.  returns the wrapped size (no null character unless
16218     included in source message).  If dest is NULL, only calculate the size required
16219     for the dest buffer.  lp argument indicats line position upon entry, and it's
16220     passed back upon exit.
16221 */
16222 int wrap(char *dest, char *src, int count, int width, int *lp)
16223 {
16224     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16225
16226     cseq_len = strlen(cseq);
16227     old_line = line = *lp;
16228     ansi = len = clen = 0;
16229
16230     for (i=0; i < count; i++)
16231     {
16232         if (src[i] == '\033')
16233             ansi = 1;
16234
16235         // if we hit the width, back up
16236         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16237         {
16238             // store i & len in case the word is too long
16239             old_i = i, old_len = len;
16240
16241             // find the end of the last word
16242             while (i && src[i] != ' ' && src[i] != '\n')
16243             {
16244                 i--;
16245                 len--;
16246             }
16247
16248             // word too long?  restore i & len before splitting it
16249             if ((old_i-i+clen) >= width)
16250             {
16251                 i = old_i;
16252                 len = old_len;
16253             }
16254
16255             // extra space?
16256             if (i && src[i-1] == ' ')
16257                 len--;
16258
16259             if (src[i] != ' ' && src[i] != '\n')
16260             {
16261                 i--;
16262                 if (len)
16263                     len--;
16264             }
16265
16266             // now append the newline and continuation sequence
16267             if (dest)
16268                 dest[len] = '\n';
16269             len++;
16270             if (dest)
16271                 strncpy(dest+len, cseq, cseq_len);
16272             len += cseq_len;
16273             line = cseq_len;
16274             clen = cseq_len;
16275             continue;
16276         }
16277
16278         if (dest)
16279             dest[len] = src[i];
16280         len++;
16281         if (!ansi)
16282             line++;
16283         if (src[i] == '\n')
16284             line = 0;
16285         if (src[i] == 'm')
16286             ansi = 0;
16287     }
16288     if (dest && appData.debugMode)
16289     {
16290         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16291             count, width, line, len, *lp);
16292         show_bytes(debugFP, src, count);
16293         fprintf(debugFP, "\ndest: ");
16294         show_bytes(debugFP, dest, len);
16295         fprintf(debugFP, "\n");
16296     }
16297     *lp = dest ? line : old_line;
16298
16299     return len;
16300 }
16301
16302 // [HGM] vari: routines for shelving variations
16303
16304 void
16305 PushInner(int firstMove, int lastMove)
16306 {
16307         int i, j, nrMoves = lastMove - firstMove;
16308
16309         // push current tail of game on stack
16310         savedResult[storedGames] = gameInfo.result;
16311         savedDetails[storedGames] = gameInfo.resultDetails;
16312         gameInfo.resultDetails = NULL;
16313         savedFirst[storedGames] = firstMove;
16314         savedLast [storedGames] = lastMove;
16315         savedFramePtr[storedGames] = framePtr;
16316         framePtr -= nrMoves; // reserve space for the boards
16317         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16318             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16319             for(j=0; j<MOVE_LEN; j++)
16320                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16321             for(j=0; j<2*MOVE_LEN; j++)
16322                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16323             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16324             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16325             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16326             pvInfoList[firstMove+i-1].depth = 0;
16327             commentList[framePtr+i] = commentList[firstMove+i];
16328             commentList[firstMove+i] = NULL;
16329         }
16330
16331         storedGames++;
16332         forwardMostMove = firstMove; // truncate game so we can start variation
16333 }
16334
16335 void
16336 PushTail(int firstMove, int lastMove)
16337 {
16338         if(appData.icsActive) { // only in local mode
16339                 forwardMostMove = currentMove; // mimic old ICS behavior
16340                 return;
16341         }
16342         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16343
16344         PushInner(firstMove, lastMove);
16345         if(storedGames == 1) GreyRevert(FALSE);
16346 }
16347
16348 void
16349 PopInner(Boolean annotate)
16350 {
16351         int i, j, nrMoves;
16352         char buf[8000], moveBuf[20];
16353
16354         storedGames--;
16355         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16356         nrMoves = savedLast[storedGames] - currentMove;
16357         if(annotate) {
16358                 int cnt = 10;
16359                 if(!WhiteOnMove(currentMove))
16360                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16361                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16362                 for(i=currentMove; i<forwardMostMove; i++) {
16363                         if(WhiteOnMove(i))
16364                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16365                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16366                         strcat(buf, moveBuf);
16367                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16368                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16369                 }
16370                 strcat(buf, ")");
16371         }
16372         for(i=1; i<=nrMoves; i++) { // copy last variation back
16373             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16374             for(j=0; j<MOVE_LEN; j++)
16375                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16376             for(j=0; j<2*MOVE_LEN; j++)
16377                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16378             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16379             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16380             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16381             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16382             commentList[currentMove+i] = commentList[framePtr+i];
16383             commentList[framePtr+i] = NULL;
16384         }
16385         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16386         framePtr = savedFramePtr[storedGames];
16387         gameInfo.result = savedResult[storedGames];
16388         if(gameInfo.resultDetails != NULL) {
16389             free(gameInfo.resultDetails);
16390       }
16391         gameInfo.resultDetails = savedDetails[storedGames];
16392         forwardMostMove = currentMove + nrMoves;
16393 }
16394
16395 Boolean
16396 PopTail(Boolean annotate)
16397 {
16398         if(appData.icsActive) return FALSE; // only in local mode
16399         if(!storedGames) return FALSE; // sanity
16400         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16401
16402         PopInner(annotate);
16403
16404         if(storedGames == 0) GreyRevert(TRUE);
16405         return TRUE;
16406 }
16407
16408 void
16409 CleanupTail()
16410 {       // remove all shelved variations
16411         int i;
16412         for(i=0; i<storedGames; i++) {
16413             if(savedDetails[i])
16414                 free(savedDetails[i]);
16415             savedDetails[i] = NULL;
16416         }
16417         for(i=framePtr; i<MAX_MOVES; i++) {
16418                 if(commentList[i]) free(commentList[i]);
16419                 commentList[i] = NULL;
16420         }
16421         framePtr = MAX_MOVES-1;
16422         storedGames = 0;
16423 }
16424
16425 void
16426 LoadVariation(int index, char *text)
16427 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16428         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16429         int level = 0, move;
16430
16431         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16432         // first find outermost bracketing variation
16433         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16434             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16435                 if(*p == '{') wait = '}'; else
16436                 if(*p == '[') wait = ']'; else
16437                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16438                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16439             }
16440             if(*p == wait) wait = NULLCHAR; // closing ]} found
16441             p++;
16442         }
16443         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16444         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16445         end[1] = NULLCHAR; // clip off comment beyond variation
16446         ToNrEvent(currentMove-1);
16447         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16448         // kludge: use ParsePV() to append variation to game
16449         move = currentMove;
16450         ParsePV(start, TRUE, TRUE);
16451         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16452         ClearPremoveHighlights();
16453         CommentPopDown();
16454         ToNrEvent(currentMove+1);
16455 }
16456